From 7f96f911c7fc825a070ebb1aa0c2e3e76cf3a9a8 Mon Sep 17 00:00:00 2001 From: BABA <38986298+BABA983@users.noreply.github.com> Date: Mon, 26 Aug 2024 20:06:06 +0800 Subject: [PATCH 001/200] Resolve custom editor with canonical resource --- src/vs/workbench/api/browser/mainThreadCustomEditors.ts | 4 +++- .../contrib/customEditor/browser/customEditors.ts | 6 ++++-- .../customEditor/common/customEditorModelManager.ts | 9 +++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts index 73c9a19d3d099..e9d0ca29eefe2 100644 --- a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts @@ -44,6 +44,7 @@ import { ResourceWorkingCopy } from '../../services/workingCopy/common/resourceW import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopySaveEvent, NO_TYPE_ID, WorkingCopyCapabilities } from '../../services/workingCopy/common/workingCopy.js'; import { IWorkingCopyFileService, WorkingCopyFileEvent } from '../../services/workingCopy/common/workingCopyFileService.js'; import { IWorkingCopyService } from '../../services/workingCopy/common/workingCopyService.js'; +import { IUriIdentityService } from '../../../platform/uriIdentity/common/uriIdentity.js'; const enum CustomEditorModelType { Custom, @@ -73,6 +74,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc @IEditorService private readonly _editorService: IEditorService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, + @IUriIdentityService private readonly _uriIdentityService: IUriIdentityService, ) { super(); @@ -197,7 +199,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc } try { - await this._proxyCustomEditors.$resolveCustomEditor(resource, handle, viewType, { + await this._proxyCustomEditors.$resolveCustomEditor(this._uriIdentityService.asCanonicalUri(resource), handle, viewType, { title: webviewInput.getTitle(), contentOptions: webviewInput.webview.contentOptions, options: webviewInput.webview.options, diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 464f9a8cb198e..d9bd443af2c86 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -21,7 +21,7 @@ import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uri import { DEFAULT_EDITOR_ASSOCIATION, EditorExtensions, GroupIdentifier, IEditorFactoryRegistry, IResourceDiffEditorInput } from '../../../common/editor.js'; import { DiffEditorInput } from '../../../common/editor/diffEditorInput.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; -import { CONTEXT_ACTIVE_CUSTOM_EDITOR_ID, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CustomEditorCapabilities, CustomEditorInfo, CustomEditorInfoCollection, ICustomEditorService } from '../common/customEditor.js'; +import { CONTEXT_ACTIVE_CUSTOM_EDITOR_ID, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CustomEditorCapabilities, CustomEditorInfo, CustomEditorInfoCollection, ICustomEditorModelManager, ICustomEditorService } from '../common/customEditor.js'; import { CustomEditorModelManager } from '../common/customEditorModelManager.js'; import { IEditorGroup, IEditorGroupContextKeyProvider, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; import { IEditorResolverService, IEditorType, RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js'; @@ -37,7 +37,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ private readonly _editorResolverDisposables = this._register(new DisposableStore()); private readonly _editorCapabilities = new Map(); - private readonly _models = new CustomEditorModelManager(); + private readonly _models: ICustomEditorModelManager; private readonly _onDidChangeEditorTypes = this._register(new Emitter()); public readonly onDidChangeEditorTypes: Event = this._onDidChangeEditorTypes.event; @@ -55,6 +55,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ ) { super(); + this._models = new CustomEditorModelManager(this.uriIdentityService); + this._contributedEditors = this._register(new ContributedCustomEditors(storageService)); // Register the contribution points only emitting one change from the resolver this.editorResolverService.bufferChangeEvents(this.registerContributionPoints.bind(this)); diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts index 66d5b2b59891e..7b5219d5e0703 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts @@ -6,9 +6,17 @@ import { createSingleCallFunction } from '../../../../base/common/functional.js'; import { IReference } from '../../../../base/common/lifecycle.js'; import { URI } from '../../../../base/common/uri.js'; +import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { ICustomEditorModel, ICustomEditorModelManager } from './customEditor.js'; export class CustomEditorModelManager implements ICustomEditorModelManager { + private readonly _uriIdentityService: IUriIdentityService; + + constructor( + uriIdentityService: IUriIdentityService, + ) { + this._uriIdentityService = uriIdentityService; + } private readonly _references = new Map Date: Tue, 26 Nov 2024 19:53:31 +0900 Subject: [PATCH 002/200] open image --- .../markdown-language-features/package.json | 15 ++++++++++++- .../package.nls.json | 1 + .../preview-src/index.ts | 6 ++++- .../src/commands/index.ts | 2 ++ .../src/commands/openImage.ts | 22 +++++++++++++++++++ .../src/preview/preview.ts | 13 +++++++++++ .../types/previewMessaging.d.ts | 7 ++++++ 7 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 extensions/markdown-language-features/src/commands/openImage.ts diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 3f1c3a5fd5814..a06a7af2f9218 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -125,6 +125,11 @@ "title": "%markdown.copyImage.title%", "category": "Markdown" }, + { + "command": "_markdown.openImage", + "title": "%markdown.openImage.title%", + "category": "Markdown" + }, { "command": "markdown.showPreview", "title": "%markdown.preview.title%", @@ -189,7 +194,11 @@ "webview/context": [ { "command": "_markdown.copyImage", - "when": "webviewId == 'markdown.preview' && webviewSection == 'image'" + "when": "webviewId == 'markdown.preview' && (webviewSection == 'image' || webviewSection == 'localImage')" + }, + { + "command": "_markdown.openImage", + "when": "webviewId == 'markdown.preview' && webviewSection == 'localImage'" } ], "editor/title": [ @@ -244,6 +253,10 @@ } ], "commandPalette": [ + { + "command": "_markdown.openImage", + "when": "false" + }, { "command": "_markdown.copyImage", "when": "false" diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index da567b46665b1..47f26b0558194 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -2,6 +2,7 @@ "displayName": "Markdown Language Features", "description": "Provides rich language support for Markdown.", "markdown.copyImage.title": "Copy Image", + "markdown.openImage.title": "Open Image", "markdown.preview.breaks.desc": "Sets how line-breaks are rendered in the Markdown preview. Setting it to `true` creates a `
` for newlines inside paragraphs.", "markdown.preview.linkify": "Convert URL-like text to links in the Markdown preview.", "markdown.preview.typographer": "Enable some language-neutral replacement and quotes beautification in the Markdown preview.", diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index 33c84e0a38477..cd2221b140d90 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -132,7 +132,11 @@ function addImageContexts() { for (const img of images) { img.id = 'image-' + idNumber; idNumber += 1; - img.setAttribute('data-vscode-context', JSON.stringify({ webviewSection: 'image', id: img.id, 'preventDefaultContextMenuItems': true, resource: documentResource })); + const imageSource = img.src; + const imgSrcAttribute = img.getAttribute('src'); + const isLocalFile = imageSource !== imgSrcAttribute; + const webviewSection = isLocalFile ? 'localImage' : 'image'; + img.setAttribute('data-vscode-context', JSON.stringify({ webviewSection, id: img.id, 'preventDefaultContextMenuItems': true, resource: documentResource, imageSource })); } } diff --git a/extensions/markdown-language-features/src/commands/index.ts b/extensions/markdown-language-features/src/commands/index.ts index e1a4f2b41ffed..382b2a8b6d511 100644 --- a/extensions/markdown-language-features/src/commands/index.ts +++ b/extensions/markdown-language-features/src/commands/index.ts @@ -18,6 +18,7 @@ import { CopyImageCommand } from './copyImage'; import { ShowPreviewSecuritySelectorCommand } from './showPreviewSecuritySelector'; import { ShowSourceCommand } from './showSource'; import { ToggleLockCommand } from './toggleLock'; +import { OpenImageCommand } from './openImage'; export function registerMarkdownCommands( commandManager: CommandManager, @@ -28,6 +29,7 @@ export function registerMarkdownCommands( ): vscode.Disposable { const previewSecuritySelector = new PreviewSecuritySelector(cspArbiter, previewManager); + commandManager.register(new OpenImageCommand(previewManager)); commandManager.register(new CopyImageCommand(previewManager)); commandManager.register(new ShowPreviewCommand(previewManager, telemetryReporter)); commandManager.register(new ShowPreviewToSideCommand(previewManager, telemetryReporter)); diff --git a/extensions/markdown-language-features/src/commands/openImage.ts b/extensions/markdown-language-features/src/commands/openImage.ts new file mode 100644 index 0000000000000..30026f7467d0f --- /dev/null +++ b/extensions/markdown-language-features/src/commands/openImage.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { Command } from '../commandManager'; +import { MarkdownPreviewManager } from '../preview/previewManager'; + +export class OpenImageCommand implements Command { + public readonly id = '_markdown.openImage'; + + public constructor( + private readonly _webviewManager: MarkdownPreviewManager, + ) { } + + public execute(args: { resource: string, imageSource: string }) { + const source = vscode.Uri.parse(args.resource); + const { fsPath } = vscode.Uri.parse(args.imageSource); + this._webviewManager.findPreview(source)?.openImage(fsPath); + } +} diff --git a/extensions/markdown-language-features/src/preview/preview.ts b/extensions/markdown-language-features/src/preview/preview.ts index 7ccbc625b4734..9e4ae20e0642f 100644 --- a/extensions/markdown-language-features/src/preview/preview.ts +++ b/extensions/markdown-language-features/src/preview/preview.ts @@ -443,6 +443,7 @@ export interface IManagedMarkdownPreview { readonly onDidChangeViewState: vscode.Event; copyImage(id: string): void; + openImage(imagePath: string): void; dispose(): void; refresh(): void; updateConfiguration(): void; @@ -524,6 +525,12 @@ export class StaticMarkdownPreview extends Disposable implements IManagedMarkdow }); } + openImage(imagePath: string): void { + const uri = vscode.Uri.file(imagePath); + this._webviewPanel.reveal(); + vscode.commands.executeCommand('vscode.open', uri); + } + private readonly _onDispose = this._register(new vscode.EventEmitter()); public readonly onDispose = this._onDispose.event; @@ -679,6 +686,12 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo }); } + openImage(imagePath: string): void { + const uri = vscode.Uri.file(imagePath); + this._webviewPanel.reveal(); + vscode.commands.executeCommand('vscode.open', uri); + } + private readonly _onDisposeEmitter = this._register(new vscode.EventEmitter()); public readonly onDispose = this._onDisposeEmitter.event; diff --git a/extensions/markdown-language-features/types/previewMessaging.d.ts b/extensions/markdown-language-features/types/previewMessaging.d.ts index 05d10af65972e..686b21bf1a91f 100644 --- a/extensions/markdown-language-features/types/previewMessaging.d.ts +++ b/extensions/markdown-language-features/types/previewMessaging.d.ts @@ -71,10 +71,17 @@ export namespace ToWebviewMessage { readonly id: string; } + export interface OpenImageContent extends BaseMessage { + readonly type: 'openImage'; + readonly source: string; + readonly imageSource: string; + } + export type Type = | OnDidChangeTextEditorSelection | UpdateView | UpdateContent | CopyImageContent + | OpenImageContent ; } From 0715a50f6370fb99110104efa067b73938d1a3f1 Mon Sep 17 00:00:00 2001 From: notoriousmango Date: Wed, 27 Nov 2024 17:03:33 +0900 Subject: [PATCH 003/200] move openImage logic to command --- .../src/commands/openImage.ts | 6 +++--- .../src/preview/preview.ts | 13 ------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/extensions/markdown-language-features/src/commands/openImage.ts b/extensions/markdown-language-features/src/commands/openImage.ts index 30026f7467d0f..7a0562db446e7 100644 --- a/extensions/markdown-language-features/src/commands/openImage.ts +++ b/extensions/markdown-language-features/src/commands/openImage.ts @@ -14,9 +14,9 @@ export class OpenImageCommand implements Command { private readonly _webviewManager: MarkdownPreviewManager, ) { } - public execute(args: { resource: string, imageSource: string }) { + public execute(args: { resource: string; imageSource: string }) { const source = vscode.Uri.parse(args.resource); - const { fsPath } = vscode.Uri.parse(args.imageSource); - this._webviewManager.findPreview(source)?.openImage(fsPath); + const imageSource = vscode.Uri.file(vscode.Uri.parse(args.imageSource).path); + vscode.commands.executeCommand('vscode.open', imageSource, this._webviewManager.findPreview(source)); } } diff --git a/extensions/markdown-language-features/src/preview/preview.ts b/extensions/markdown-language-features/src/preview/preview.ts index 9e4ae20e0642f..7ccbc625b4734 100644 --- a/extensions/markdown-language-features/src/preview/preview.ts +++ b/extensions/markdown-language-features/src/preview/preview.ts @@ -443,7 +443,6 @@ export interface IManagedMarkdownPreview { readonly onDidChangeViewState: vscode.Event; copyImage(id: string): void; - openImage(imagePath: string): void; dispose(): void; refresh(): void; updateConfiguration(): void; @@ -525,12 +524,6 @@ export class StaticMarkdownPreview extends Disposable implements IManagedMarkdow }); } - openImage(imagePath: string): void { - const uri = vscode.Uri.file(imagePath); - this._webviewPanel.reveal(); - vscode.commands.executeCommand('vscode.open', uri); - } - private readonly _onDispose = this._register(new vscode.EventEmitter()); public readonly onDispose = this._onDispose.event; @@ -686,12 +679,6 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo }); } - openImage(imagePath: string): void { - const uri = vscode.Uri.file(imagePath); - this._webviewPanel.reveal(); - vscode.commands.executeCommand('vscode.open', uri); - } - private readonly _onDisposeEmitter = this._register(new vscode.EventEmitter()); public readonly onDispose = this._onDisposeEmitter.event; From ea6463e38c6050c5b8dcf3bd9543483435cd8fd4 Mon Sep 17 00:00:00 2001 From: notoriousmango Date: Wed, 27 Nov 2024 17:25:41 +0900 Subject: [PATCH 004/200] rename --- .../markdown-language-features/src/commands/openImage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/markdown-language-features/src/commands/openImage.ts b/extensions/markdown-language-features/src/commands/openImage.ts index 7a0562db446e7..53f615148f37b 100644 --- a/extensions/markdown-language-features/src/commands/openImage.ts +++ b/extensions/markdown-language-features/src/commands/openImage.ts @@ -16,7 +16,7 @@ export class OpenImageCommand implements Command { public execute(args: { resource: string; imageSource: string }) { const source = vscode.Uri.parse(args.resource); - const imageSource = vscode.Uri.file(vscode.Uri.parse(args.imageSource).path); - vscode.commands.executeCommand('vscode.open', imageSource, this._webviewManager.findPreview(source)); + const imageSourceUri = vscode.Uri.file(vscode.Uri.parse(args.imageSource).path); + vscode.commands.executeCommand('vscode.open', imageSourceUri, this._webviewManager.findPreview(source)); } } From 831c95988ebaf1f83e0459922e699072ce4249d3 Mon Sep 17 00:00:00 2001 From: adrianstephens Date: Fri, 6 Dec 2024 17:12:25 -0800 Subject: [PATCH 005/200] custom editor preview --- src/vs/workbench/browser/parts/editor/editorCommands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 1cecdaab32f09..83f88bd7b749d 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -494,7 +494,7 @@ function registerOpenEditorAPICommands(): void { const [columnArg, optionsArg] = columnAndOptions ?? []; - await editorService.openEditor({ resource: URI.from(resource, true), options: { ...optionsArg, pinned: true, override: id } }, columnToEditorGroup(editorGroupsService, configurationService, columnArg)); + await editorService.openEditor({ resource: URI.from(resource, true), options: { pinned: true, ...optionsArg, override: id } }, columnToEditorGroup(editorGroupsService, configurationService, columnArg)); }); // partial, renderer-side API command to open diff editor From e38f760f3d66339120c9665e27e168c44a84e2ff Mon Sep 17 00:00:00 2001 From: RedCMD Date: Thu, 12 Dec 2024 14:04:59 +1300 Subject: [PATCH 006/200] Fix extension preview codeblock language getter --- .../markdown/browser/markdownDocumentRenderer.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts index 30f5fd504076d..e80722d7993d3 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts @@ -234,7 +234,7 @@ export async function renderMarkdownDocument( namespace MarkedHighlight { // Copied from https://github.com/markedjs/marked-highlight/blob/main/src/index.js - export function markedHighlight(options: marked.MarkedOptions & { highlight: (code: string, lang: string, info: string) => string | Promise }): marked.MarkedExtension { + export function markedHighlight(options: marked.MarkedOptions & { highlight: (code: string, lang: string) => string | Promise }): marked.MarkedExtension { if (typeof options === 'function') { options = { highlight: options, @@ -252,13 +252,11 @@ namespace MarkedHighlight { return; } - const lang = getLang(token.lang); - if (options.async) { - return Promise.resolve(options.highlight(token.text, lang, token.lang || '')).then(updateToken(token)); + return Promise.resolve(options.highlight(token.text, token.lang)).then(updateToken(token)); } - const code = options.highlight(token.text, lang, token.lang || ''); + const code = options.highlight(token.text, token.lang); if (code instanceof Promise) { throw new Error('markedHighlight is not set to async but the highlight function is async. Set the async option to true on markedHighlight to await the async highlight function.'); } @@ -276,10 +274,6 @@ namespace MarkedHighlight { }; } - function getLang(lang: string) { - return (lang || '').match(/\S*/)![0]; - } - function updateToken(token: any) { return (code: string) => { if (typeof code === 'string' && code !== token.text) { From 1e3788f49f234bc37296ce3c5c7b1645605cec08 Mon Sep 17 00:00:00 2001 From: Michael H Date: Fri, 13 Dec 2024 13:29:39 +1100 Subject: [PATCH 007/200] bun.lock --- extensions/npm/src/preferred-pm.ts | 4 ++++ src/vs/workbench/contrib/files/browser/files.contribution.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/extensions/npm/src/preferred-pm.ts b/extensions/npm/src/preferred-pm.ts index c85b65b6ea35e..452319671eaf7 100644 --- a/extensions/npm/src/preferred-pm.ts +++ b/extensions/npm/src/preferred-pm.ts @@ -28,6 +28,10 @@ async function isBunPreferred(pkgPath: string): Promise { return { isPreferred: true, hasLockfile: true }; } + if (await pathExists(path.join(pkgPath, 'bun.lock'))) { + return { isPreferred: true, hasLockfile: true }; + } + return { isPreferred: false, hasLockfile: false }; } diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 266995ddc6d2d..689be82dc1664 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -619,7 +619,7 @@ configurationRegistry.registerConfiguration({ '*.jsx': '${capture}.js', '*.tsx': '${capture}.ts', 'tsconfig.json': 'tsconfig.*.json', - 'package.json': 'package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb', + 'package.json': 'package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb, bun.lock', } } } From f7c3b1b4748ba07ddf14dafd9b1dc611372c1bde Mon Sep 17 00:00:00 2001 From: notoriousmango Date: Sun, 15 Dec 2024 00:01:28 +0900 Subject: [PATCH 008/200] use openDocumentLink --- extensions/markdown-language-features/preview-src/index.ts | 6 +++--- .../markdown-language-features/src/commands/openImage.ts | 3 +-- .../src/preview/previewManager.ts | 5 +++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index cd2221b140d90..1c2e2f5535ad3 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -11,6 +11,7 @@ import { SettingsManager, getData } from './settings'; import throttle = require('lodash.throttle'); import morphdom from 'morphdom'; import type { ToWebviewMessage } from '../types/previewMessaging'; +import { isOfScheme, Schemes } from '../src/util/schemes'; let scrollDisabledCount = 0; @@ -132,9 +133,8 @@ function addImageContexts() { for (const img of images) { img.id = 'image-' + idNumber; idNumber += 1; - const imageSource = img.src; - const imgSrcAttribute = img.getAttribute('src'); - const isLocalFile = imageSource !== imgSrcAttribute; + const imageSource = img.getAttribute('data-src'); + const isLocalFile = imageSource && !(isOfScheme(Schemes.http, imageSource) || isOfScheme(Schemes.https, imageSource)); const webviewSection = isLocalFile ? 'localImage' : 'image'; img.setAttribute('data-vscode-context', JSON.stringify({ webviewSection, id: img.id, 'preventDefaultContextMenuItems': true, resource: documentResource, imageSource })); } diff --git a/extensions/markdown-language-features/src/commands/openImage.ts b/extensions/markdown-language-features/src/commands/openImage.ts index 53f615148f37b..64b1831df0d98 100644 --- a/extensions/markdown-language-features/src/commands/openImage.ts +++ b/extensions/markdown-language-features/src/commands/openImage.ts @@ -16,7 +16,6 @@ export class OpenImageCommand implements Command { public execute(args: { resource: string; imageSource: string }) { const source = vscode.Uri.parse(args.resource); - const imageSourceUri = vscode.Uri.file(vscode.Uri.parse(args.imageSource).path); - vscode.commands.executeCommand('vscode.open', imageSourceUri, this._webviewManager.findPreview(source)); + this._webviewManager.openDocumentLink(args.imageSource, source); } } diff --git a/extensions/markdown-language-features/src/preview/previewManager.ts b/extensions/markdown-language-features/src/preview/previewManager.ts index 3b46c2a432203..3aa126da063d6 100644 --- a/extensions/markdown-language-features/src/preview/previewManager.ts +++ b/extensions/markdown-language-features/src/preview/previewManager.ts @@ -170,6 +170,11 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview } } + public openDocumentLink(linkText: string, fromResource: vscode.Uri) { + const viewColumn = this.findPreview(fromResource)?.resourceColumn; + return this._opener.openDocumentLink(linkText, fromResource, viewColumn); + } + public async deserializeWebviewPanel( webview: vscode.WebviewPanel, state: any From 58399f1f6840737cece0a8b6ad065d51422b4987 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 16 Dec 2024 21:22:49 +0100 Subject: [PATCH 009/200] Git - truncate the git blame information after 50 characters (#236279) --- extensions/git/src/blame.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/extensions/git/src/blame.ts b/extensions/git/src/blame.ts index b625439e15f0f..916d906a0a21a 100644 --- a/extensions/git/src/blame.ts +++ b/extensions/git/src/blame.ts @@ -144,6 +144,8 @@ class GitBlameInformationCache { } export class GitBlameController { + private readonly _subjectMaxLength = 50; + private readonly _onDidChangeBlameInformation = new EventEmitter(); public readonly onDidChangeBlameInformation = this._onDidChangeBlameInformation.event; @@ -169,10 +171,14 @@ export class GitBlameController { } formatBlameInformationMessage(template: string, blameInformation: BlameInformation): string { + const subject = blameInformation.subject && blameInformation.subject.length > this._subjectMaxLength + ? `${blameInformation.subject.substring(0, this._subjectMaxLength)}\u2026` + : blameInformation.subject; + const templateTokens = { hash: blameInformation.hash, hashShort: blameInformation.hash.substring(0, 8), - subject: emojify(blameInformation.subject ?? ''), + subject: emojify(subject ?? ''), authorName: blameInformation.authorName ?? '', authorEmail: blameInformation.authorEmail ?? '', authorDate: new Date(blameInformation.authorDate ?? new Date()).toLocaleString(), From f1a2ce694a2899821233834096cf4acaf9ea81e2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Dec 2024 21:52:59 +0100 Subject: [PATCH 010/200] remove rarely used reinstall extension command (#236278) --- .../abstractExtensionManagementService.ts | 1 - .../common/extensionManagement.ts | 1 - .../common/extensionManagementIpc.ts | 7 -- .../node/extensionManagementService.ts | 22 ------- .../browser/extensions.contribution.ts | 13 +--- .../extensions/browser/extensionsActions.ts | 66 ------------------- .../browser/extensionsWorkbenchService.ts | 11 ---- .../contrib/extensions/common/extensions.ts | 1 - .../common/extensionManagementService.ts | 9 --- .../common/webExtensionManagementService.ts | 1 - .../test/browser/workbenchTestServices.ts | 3 - 11 files changed, 1 insertion(+), 134 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 59f359d659c29..6b854b0bd87e9 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -111,7 +111,6 @@ export abstract class CommontExtensionManagementService extends Disposable imple 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; } diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index ba8a8e86feefd..155079831fcc7 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -572,7 +572,6 @@ export interface IExtensionManagementService { uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise; uninstallExtensions(extensions: UninstallExtensionInfo[]): Promise; toggleAppliationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise; - reinstallFromGallery(extension: ILocalExtension): Promise; getInstalled(type?: ExtensionType, profileLocation?: URI, productVersion?: IProductVersion): Promise; getExtensionsControlManifest(): Promise; copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise; diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index d9542cb5e6a98..e20ce088e42b5 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -144,9 +144,6 @@ export class ExtensionManagementChannel implements IServerChannel { const arg: UninstallExtensionInfo[] = args[0]; return this.service.uninstallExtensions(arg.map(({ extension, options }) => ({ extension: transformIncomingExtension(extension, uriTransformer), options: transformIncomingOptions(options, uriTransformer) }))); } - case 'reinstallFromGallery': { - return this.service.reinstallFromGallery(transformIncomingExtension(args[0], uriTransformer)); - } case 'getInstalled': { const extensions = await this.service.getInstalled(args[0], transformIncomingURI(args[1], uriTransformer), args[2]); return extensions.map(e => transformOutgoingExtension(e, uriTransformer)); @@ -299,10 +296,6 @@ export class ExtensionManagementChannelClient extends CommontExtensionManagement } - reinstallFromGallery(extension: ILocalExtension): Promise { - return Promise.resolve(this.channel.call('reinstallFromGallery', [extension])).then(local => transformIncomingExtension(local, null)); - } - getInstalled(type: ExtensionType | null = null, extensionsProfileResource?: URI, productVersion?: IProductVersion): Promise { return Promise.resolve(this.channel.call('getInstalled', [type, extensionsProfileResource, productVersion])) .then(extensions => extensions.map(extension => transformIncomingExtension(extension, null))); diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index a55978c612f26..92405eefb753c 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -8,7 +8,6 @@ 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'; @@ -215,27 +214,6 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return local; } - 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")); - } - - const targetPlatform = await this.getTargetPlatform(); - 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")); - } - - 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))); - } - return this.installFromGallery(galleryExtension); - } - protected copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial): Promise { return this.extensionsScanner.copyExtension(extension, fromProfileLocation, toProfileLocation, metadata); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 4a2b68d2f9122..4a55c93c25c8c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -14,7 +14,7 @@ import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsServi 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, EXTENSIONS_CATEGORY } from '../common/extensions.js'; -import { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction, ClearLanguageAction, ToggleAutoUpdateForExtensionAction, ToggleAutoUpdatesForPublisherAction, TogglePreReleaseExtensionAction, InstallAnotherVersionAction, InstallAction } from './extensionsActions.js'; +import { 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, ExtensionsSearchValueContext } from './extensionsViewlet.js'; @@ -1292,17 +1292,6 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi }, run: () => runAction(this.instantiationService.createInstance(InstallSpecificVersionOfExtensionAction, InstallSpecificVersionOfExtensionAction.ID, InstallSpecificVersionOfExtensionAction.LABEL)) }); - - this.registerExtensionAction({ - id: ReinstallAction.ID, - 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)) - }); } // Extension Context Menu diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index a932e4f9ab502..052e14050eb7c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -2843,72 +2843,6 @@ export class ExtensionStatusAction extends ExtensionAction { } } -export class ReinstallAction extends Action { - - 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, - @IHostService private readonly hostService: IHostService, - @IExtensionService private readonly extensionService: IExtensionService - ) { - super(id, label); - } - - override get enabled(): boolean { - 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; - }); - } - - private reinstallExtension(extension: IExtension): Promise { - 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)); - }); - } -} - export class InstallSpecificVersionOfExtensionAction extends Action { static readonly ID = 'workbench.extensions.action.install.specificVersion'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 1a862498873ed..1a28f0027eec6 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -2618,17 +2618,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension 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; - if (!toReinstall) { - throw new Error('Missing local'); - } - return this.extensionManagementService.reinstallFromGallery(toReinstall); - }); - } - isExtensionIgnoredToSync(extension: IExtension): boolean { return extension.local ? !this.isInstalledExtensionSynced(extension.local) : this.extensionsSyncManagementService.hasToNeverSyncExtension(extension.identifier.id); diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 916628ddc604b..4cd9d5ea1d4e2 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -143,7 +143,6 @@ export interface IExtensionsWorkbenchService { 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; diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 6312b05fe32ab..d82368283243a 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -256,15 +256,6 @@ export class ExtensionManagementService extends Disposable implements IWorkbench } - async reinstallFromGallery(extension: ILocalExtension): Promise { - const server = this.getServer(extension); - if (server) { - await this.checkForWorkspaceTrust(extension.manifest, false); - return server.extensionManagementService.reinstallFromGallery(extension); - } - return Promise.reject(`Invalid location ${extension.location.toString()}`); - } - updateMetadata(extension: ILocalExtension, metadata: Partial): Promise { const server = this.getServer(extension); if (server) { diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index 30971801a829b..65b49fb7bd124 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -201,7 +201,6 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe zip(extension: ILocalExtension): Promise { throw new Error('unsupported'); } getManifest(vsix: URI): Promise { throw new Error('unsupported'); } download(): Promise { throw new Error('unsupported'); } - reinstallFromGallery(): Promise { throw new Error('unsupported'); } async cleanUp(): Promise { } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 4a850d040d8d2..a914f4586d4c4 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -2226,9 +2226,6 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens uninstallExtensions(extensions: UninstallExtensionInfo[]): Promise { throw new Error('Method not implemented.'); } - async reinstallFromGallery(extension: ILocalExtension): Promise { - throw new Error('Method not implemented.'); - } async getInstalled(type?: ExtensionType | undefined): Promise { return []; } getExtensionsControlManifest(): Promise { throw new Error('Method not implemented.'); From 40582112ab4e36a7e01493973f6f2d25148b6cf2 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 16 Dec 2024 21:53:25 +0100 Subject: [PATCH 011/200] SCM - more quick diff cleanup (#236280) --- .../contrib/scm/browser/quickDiffModel.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts b/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts index 9907a627c27e7..3d1ec39c3f61b 100644 --- a/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts +++ b/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts @@ -33,8 +33,14 @@ export const IQuickDiffModelService = createDecorator('I export interface QuickDiffModelOptions { readonly algorithm: DiffAlgorithmName; + readonly maxComputationTimeMs?: number; } +const decoratorQuickDiffModelOptions: QuickDiffModelOptions = { + algorithm: 'legacy', + maxComputationTimeMs: 1000 +}; + export interface IQuickDiffModelService { _serviceBrand: undefined; @@ -52,7 +58,7 @@ class QuickDiffModelReferenceCollection extends ReferenceCollection | undefined { + createQuickDiffModelReference(resource: URI, options: QuickDiffModelOptions = decoratorQuickDiffModelOptions): IReference | undefined { const textFileModel = this.textFileService.files.get(resource); if (!textFileModel?.isResolved()) { return undefined; } - resource = options === undefined - ? this.uriIdentityService.asCanonicalUri(resource) - : this.uriIdentityService.asCanonicalUri(resource).with({ query: JSON.stringify(options) }); - + resource = this.uriIdentityService.asCanonicalUri(resource).with({ query: JSON.stringify(options) }); return this._references.acquire(resource.toString(), textFileModel, options); } } @@ -119,7 +122,7 @@ export class QuickDiffModel extends Disposable { constructor( textFileModel: IResolvedTextFileEditorModel, - private readonly options: QuickDiffModelOptions | undefined, + private readonly options: QuickDiffModelOptions, @ISCMService private readonly scmService: ISCMService, @IQuickDiffService private readonly quickDiffService: IQuickDiffService, @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, @@ -273,14 +276,11 @@ export class QuickDiffModel extends Disposable { } private async _diff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise<{ changes: readonly IChange[] | null; changes2: readonly LineRangeMapping[] | null }> { - // When no algorithm is specified, we use the legacy diff algorithm along with a 1000ms - // timeout as the diff information is being used for the quick diff editor decorations - const algorithm = this.options?.algorithm ?? 'legacy'; - const maxComputationTimeMs = this.options?.algorithm ? Number.MAX_SAFE_INTEGER : 1000; + const maxComputationTimeMs = this.options.maxComputationTimeMs ?? Number.MAX_SAFE_INTEGER; const result = await this.editorWorkerService.computeDiff(original, modified, { computeMoves: false, ignoreTrimWhitespace, maxComputationTimeMs - }, algorithm); + }, this.options.algorithm); return { changes: result ? toLineChanges(DiffState.fromDiffResult(result)) : null, changes2: result?.changes ?? null }; } @@ -361,7 +361,7 @@ export class QuickDiffModel extends Disposable { // When the QuickDiffModel is created for a diff editor, there is no // need to compute the diff information for the `isSCM` quick diff // provider as that information will be provided by the diff editor - return this.options?.algorithm !== undefined + return this.options.maxComputationTimeMs === undefined ? quickDiffs.filter(quickDiff => !quickDiff.isSCM) : quickDiffs; } From 057dd7b933f684d018a614a1ce03e4db5c3614c9 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 16 Dec 2024 15:31:45 -0600 Subject: [PATCH 012/200] get `No suggestions` to show up when triggered for terminal suggest (#236264) --- .../suggest/browser/terminalSuggestAddon.ts | 3 +-- .../suggest/browser/simpleSuggestWidget.ts | 24 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index 81476106cf270..331d36a2e58af 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -42,7 +42,6 @@ export interface ISuggestController { acceptSelectedSuggestion(suggestion?: Pick): void; hideSuggestWidget(): void; } - export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggestController { private _terminal?: Terminal; @@ -366,7 +365,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest } const suggestWidget = this._ensureSuggestWidget(this._terminal); suggestWidget.setCompletionModel(model); - if (model.items.length === 0 || !this._promptInputModel) { + if (!this._promptInputModel) { return; } this._model = model; diff --git a/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts b/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts index 6f22b0d0d77eb..61fb7437cea94 100644 --- a/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts +++ b/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts @@ -59,6 +59,9 @@ export interface IWorkbenchSuggestWidgetOptions { export class SimpleSuggestWidget extends Disposable { + private static LOADING_MESSAGE: string = localize('suggestWidget.loading', "Loading..."); + private static NO_SUGGESTIONS_MESSAGE: string = localize('suggestWidget.noSuggestions', "No suggestions."); + private _state: State = State.Hidden; private _completionModel?: SimpleCompletionModel; private _cappedHeight?: { wanted: number; capped: number }; @@ -67,6 +70,7 @@ export class SimpleSuggestWidget extends Disposable { private readonly _pendingLayout = this._register(new MutableDisposable()); readonly element: ResizableHTMLElement; + private readonly _messageElement: HTMLElement; private readonly _listElement: HTMLElement; private readonly _list: List; private readonly _status?: SuggestWidgetStatus; @@ -193,6 +197,8 @@ export class SimpleSuggestWidget extends Disposable { } })); + this._messageElement = dom.append(this.element.domNode, dom.$('.message')); + if (options.statusBarMenuId) { this._status = this._register(instantiationService.createInstance(SuggestWidgetStatus, this.element.domNode, options.statusBarMenuId)); this.element.domNode.classList.toggle('with-status-bar', true); @@ -287,7 +293,9 @@ export class SimpleSuggestWidget extends Disposable { switch (state) { case State.Hidden: - // dom.hide(this._messageElement, this._listElement, this._status.element); + if (this._status) { + dom.hide(this._messageElement, this._listElement, this._status.element); + } dom.hide(this._listElement); if (this._status) { dom.hide(this._status?.element); @@ -307,30 +315,30 @@ export class SimpleSuggestWidget extends Disposable { break; case State.Loading: this.element.domNode.classList.add('message'); - // this._messageElement.textContent = SuggestWidget.LOADING_MESSAGE; + this._messageElement.textContent = SimpleSuggestWidget.LOADING_MESSAGE; dom.hide(this._listElement); if (this._status) { dom.hide(this._status?.element); } - // dom.show(this._messageElement); + dom.show(this._messageElement); // this._details.hide(); this._show(); // this._focusedItem = undefined; break; case State.Empty: this.element.domNode.classList.add('message'); - // this._messageElement.textContent = SuggestWidget.NO_SUGGESTIONS_MESSAGE; + this._messageElement.textContent = SimpleSuggestWidget.NO_SUGGESTIONS_MESSAGE; dom.hide(this._listElement); if (this._status) { dom.hide(this._status?.element); } - // dom.show(this._messageElement); + dom.show(this._messageElement); // this._details.hide(); this._show(); // this._focusedItem = undefined; break; case State.Open: - // dom.hide(this._messageElement); + dom.hide(this._messageElement); dom.show(this._listElement); if (this._status) { dom.show(this._status?.element); @@ -338,7 +346,7 @@ export class SimpleSuggestWidget extends Disposable { this._show(); break; case State.Frozen: - // dom.hide(this._messageElement); + dom.hide(this._messageElement); dom.show(this._listElement); if (this._status) { dom.show(this._status?.element); @@ -346,7 +354,7 @@ export class SimpleSuggestWidget extends Disposable { this._show(); break; case State.Details: - // dom.hide(this._messageElement); + dom.hide(this._messageElement); dom.show(this._listElement); if (this._status) { dom.show(this._status?.element); From d0ecdc1f5551d21f3d7822c8adc026ad4a9bf7e3 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 16 Dec 2024 16:38:14 -0600 Subject: [PATCH 013/200] get terminal editor's name to persist upon rename cancellation (#236281) fix #235931 --- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 45d04fac45148..9218eff9f83b0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -1724,7 +1724,9 @@ async function renameWithQuickPick(c: ITerminalServicesCollection, accessor: Ser value: instance.title, prompt: localize('workbench.action.terminal.rename.prompt', "Enter terminal name"), }); - instance.rename(title); + if (title) { + instance.rename(title); + } } } From 8264792d81f87d1524f4c26b07e571a28d3fc24a Mon Sep 17 00:00:00 2001 From: Oleg Solomko Date: Mon, 16 Dec 2024 15:07:15 -0800 Subject: [PATCH 014/200] add prompt snippets support (#234220) * [prompt snippets]: working on recursive file references * [prompt snippets]: working on file contents codec * [prompt snippets]: implement the `LinesCodecDecoder` * [prompt snippets]: working on the `SimpleTokensCodecDecoder` * [prompt snippets]: implement first version of `PromptSyntaxCodec` * [prompt snippets]: add `Range` attributes to all the tokens * [prompt snippets]: refactoring components to standalone files * [prompt snippets]: move out components to separate files, working on the first unit test * [prompt snippets]: add the first unit test for `LinesDecoder` * [prompt snippets]: add first unit test for the `SimpleDecoder` * [prompt snippets]: refactor `SimpleDecoder` logic * [prompt snippets]: make `SimpleDecoder` handle `\t` characters and improve unit tests * [prompt snippets]: add unit tests for the `ChatbotPromptCodec` codec * [prompt snippets]: add unit test for the `ChatbotPromptReference` * [prompt snippets]: working on enhancing prompt context * [prompt snippets]: enahnce working set file references on submit * [prompt snippets]: cleanup * [prompt snippets]: implement automatic references resolution on file variable addition * [prompt snippets]: implement prompt snippets for implicit context files * [prompt snippets]: improve implicit context file rendering * [prompt snippets]: remove redundant `ChatReference` class * [prompt snippets]: `PromptReference` can now subscribe to filesystem changes, added unit test for the `assertDefined` util * [prompt snippets]: make `LinesDecoder` to also emit `NewLine` tokens, add more unit tests for it * [prompt snippets]: resolve nested references for dynamic variables and update variable label * [prompt snippets]: add nested file references to implicit context of prompt request * [prompt snippets]: variable hover label now includes nested file references * [prompt snippets]: add the `TestDecoder` and `TestLinesDecoder` reusable test utilities * [prompt snippets]: add unit tests for `ChatbotPromptDecoder` decoder * [prompt snippets]: refactor decoders to eliminate static type hacks * [prompt snippets]: improve `BaseDecoder` logic and docs * [prompt snippets]: add unit tests for the new reduce-less logic of the stream * [prompt snippets]: improve doc comments, finish the basic unit test for the `PromptFileReference` class * [prompt snippets]: finish the main unit test for the `PromptFileReference` class * [prompt snippets]: make the `PromptFileReference` class ininite-reference-recursion-proof and add approptiate tests * [prompt snippets]: cleanup * [prompt snippets]: ensure codecs work well with UTF16 codepoints * [prompt snippets]: add `randomInt()` utility and related tests * [prompt snippets]: refactor and add more unit tests * [prompt snippets]: fix unresolved `Buffer` import reference * [prompt snippets]: refactor `LinesDecoder` to use `VSBuffer` as underlying data source * [prompt snippets]: make the `ImplicitContext` to create prompt file reference object only if `prompt-snippets` config value is set * [prompt snippets]: make the `ChatDynamicVariable` to resolve prompt file reference object only if `prompt-snippets` config value is set * [prompt snippets]: localize (+N more) labels * [prompt snippets]: pre PR cleanups * [prompt snippets]: minor cleanups * [prompt snippets]: address some PR feedback, fix an unit test issue * [prompt snippets]: move file to common layers * [prompt snippets]: move base codec types to base/common * [prompt snippets]: move more codec files around * [prompt snippets]: update a code comment * [prompt snippets]: fix `assert.throws()` API incompatibility issue * [prompt snippets]: minor cleanup * [prompt snippets]: improve unit tests for the `PromptFileReference` * [prompt snippets]: skip unit tests of `PromptFileReference` on Windows for now * [prompt snippets]: address PR feedback * [prompt snippets]: add `PromptFileReference.isPromptSnippetFile` getter * [prompt snippets]: move the `assertDefined` utility to `types.ts` * use service injection in `PromptFileReference` class * [prompt snippets]: remove formatting changes * [prompt snippets]: remove more formatting changes * [prompt snippets]: revert more formatting changes * [prompt snippets]: add logic to dispose existing variables in `ChatDynamicVariableModel` * [prompt snippets]: cleanup * [prompt snippets]: minor cleanup * [prompt snippets]: remove redundant disposable registrations, cleanup more formatting changes * [prompt snippets]: improve cross-platform newline handling, add `vertical tab` and `form feed` tokens * [prompt snippets]: minor types naming cleanup * [prompt snippets]: make `ChatPromptCodec` to be a global singleton object and improve doc comments * [prompt snippets]: address PR feedback * [prompt snippets]: remove (+N more) label, ensure that `ChatFileReferences` are scoped to files only * [prompt snippets]: revert changes for the `ImplicitContext` * [prompt snippets]: cleanup changes in `ChatDynamicVariableModel` * [prompt snippets]: revert changes in the `ChatRequestParser` * [prompt snippets]: improve history navigation * [prompt snippets]: address PR feedback * [prompt snippets]: allow non-prompt-snippet files to be referenced, but don't parse their contents --- src/vs/base/common/codecs/asyncDecoder.ts | 85 ++++ src/vs/base/common/codecs/baseDecoder.ts | 322 ++++++++++++ src/vs/base/common/codecs/types/ICodec.d.ts | 22 + src/vs/base/common/numbers.ts | 62 +++ src/vs/base/common/stream.ts | 36 +- src/vs/base/common/types.ts | 47 +- src/vs/base/test/common/numbers.test.ts | 177 ++++++- src/vs/base/test/common/stream.test.ts | 100 +++- src/vs/base/test/common/types.test.ts | 130 +++++ src/vs/editor/common/codecs/baseToken.ts | 57 +++ .../common/codecs/linesCodec/linesDecoder.ts | 213 ++++++++ .../linesCodec/tokens/carriageReturn.ts | 59 +++ .../common/codecs/linesCodec/tokens/line.ts | 63 +++ .../codecs/linesCodec/tokens/newLine.ts | 58 +++ .../codecs/simpleCodec/simpleDecoder.ts | 100 ++++ .../codecs/simpleCodec/tokens/formFeed.ts | 46 ++ .../common/codecs/simpleCodec/tokens/space.ts | 46 ++ .../common/codecs/simpleCodec/tokens/tab.ts | 47 ++ .../codecs/simpleCodec/tokens/verticalTab.ts | 46 ++ .../common/codecs/simpleCodec/tokens/word.ts | 72 +++ .../test/common/codecs/linesDecoder.test.ts | 140 ++++++ .../test/common/codecs/simpleDecoder.test.ts | 111 ++++ .../editor/test/common/utils/testDecoder.ts | 115 +++++ .../contrib/chat/browser/chatInputPart.ts | 25 +- .../contrib/chat/browser/chatWidget.ts | 2 +- .../browser/contrib/chatDynamicVariables.ts | 96 +++- .../chatDynamicVariables/chatFileReference.ts | 79 +++ .../codecs/chatPromptCodec/chatPromptCodec.ts | 52 ++ .../chatPromptCodec/chatPromptDecoder.ts | 45 ++ .../chatPromptCodec/tokens/fileReference.ts | 98 ++++ .../chat/common/promptFileReference.ts | 414 +++++++++++++++ .../chat/common/promptFileReferenceErrors.ts | 129 +++++ .../common/codecs/chatPromptCodec.test.ts | 77 +++ .../common/codecs/chatPromptDecoder.test.ts | 75 +++ .../test/common/promptFileReference.test.ts | 474 ++++++++++++++++++ 35 files changed, 3687 insertions(+), 33 deletions(-) create mode 100644 src/vs/base/common/codecs/asyncDecoder.ts create mode 100644 src/vs/base/common/codecs/baseDecoder.ts create mode 100644 src/vs/base/common/codecs/types/ICodec.d.ts create mode 100644 src/vs/editor/common/codecs/baseToken.ts create mode 100644 src/vs/editor/common/codecs/linesCodec/linesDecoder.ts create mode 100644 src/vs/editor/common/codecs/linesCodec/tokens/carriageReturn.ts create mode 100644 src/vs/editor/common/codecs/linesCodec/tokens/line.ts create mode 100644 src/vs/editor/common/codecs/linesCodec/tokens/newLine.ts create mode 100644 src/vs/editor/common/codecs/simpleCodec/simpleDecoder.ts create mode 100644 src/vs/editor/common/codecs/simpleCodec/tokens/formFeed.ts create mode 100644 src/vs/editor/common/codecs/simpleCodec/tokens/space.ts create mode 100644 src/vs/editor/common/codecs/simpleCodec/tokens/tab.ts create mode 100644 src/vs/editor/common/codecs/simpleCodec/tokens/verticalTab.ts create mode 100644 src/vs/editor/common/codecs/simpleCodec/tokens/word.ts create mode 100644 src/vs/editor/test/common/codecs/linesDecoder.test.ts create mode 100644 src/vs/editor/test/common/codecs/simpleDecoder.test.ts create mode 100644 src/vs/editor/test/common/utils/testDecoder.ts create mode 100644 src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables/chatFileReference.ts create mode 100644 src/vs/workbench/contrib/chat/common/codecs/chatPromptCodec/chatPromptCodec.ts create mode 100644 src/vs/workbench/contrib/chat/common/codecs/chatPromptCodec/chatPromptDecoder.ts create mode 100644 src/vs/workbench/contrib/chat/common/codecs/chatPromptCodec/tokens/fileReference.ts create mode 100644 src/vs/workbench/contrib/chat/common/promptFileReference.ts create mode 100644 src/vs/workbench/contrib/chat/common/promptFileReferenceErrors.ts create mode 100644 src/vs/workbench/contrib/chat/test/common/codecs/chatPromptCodec.test.ts create mode 100644 src/vs/workbench/contrib/chat/test/common/codecs/chatPromptDecoder.test.ts create mode 100644 src/vs/workbench/contrib/chat/test/common/promptFileReference.test.ts diff --git a/src/vs/base/common/codecs/asyncDecoder.ts b/src/vs/base/common/codecs/asyncDecoder.ts new file mode 100644 index 0000000000000..efbcf9a7fe4e4 --- /dev/null +++ b/src/vs/base/common/codecs/asyncDecoder.ts @@ -0,0 +1,85 @@ +/*--------------------------------------------------------------------------------------------- + * 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 '../lifecycle.js'; +import { BaseDecoder } from './baseDecoder.js'; + +/** + * Asynchronous interator wrapper for a decoder. + */ +export class AsyncDecoder, K extends NonNullable = NonNullable> extends Disposable { + // Buffer of messages that have been decoded but not yet consumed. + private readonly messages: T[] = []; + + /** + * A transient promise that is resolved when a new event + * is received. Used in the situation when there is no new + * data avaialble and decoder stream did not finish yet, + * hence we need to wait until new event is received. + */ + private resolveOnNewEvent?: (value: void) => void; + + /** + * @param decoder The decoder instance to wrap. + * + * Note! Assumes ownership of the `decoder` object, hence will `dipose` + * it when the decoder stream is ended. + */ + constructor( + private readonly decoder: BaseDecoder, + ) { + super(); + + this._register(decoder); + } + + /** + * Async iterator implementation. + */ + async *[Symbol.asyncIterator](): AsyncIterator { + // callback is called when `data` or `end` event is received + const callback = (data?: T) => { + if (data !== undefined) { + this.messages.push(data); + } else { + this.decoder.removeListener('data', callback); + this.decoder.removeListener('end', callback); + } + + // is the promise resolve callback is present, + // then call it and remove the reference + if (this.resolveOnNewEvent) { + this.resolveOnNewEvent(); + delete this.resolveOnNewEvent; + } + }; + this.decoder.on('data', callback); + this.decoder.on('end', callback); + + // start flowing the decoder stream + this.decoder.start(); + + while (true) { + const maybeMessage = this.messages.shift(); + if (maybeMessage !== undefined) { + yield maybeMessage; + continue; + } + + // if no data available and stream ended, we're done + if (this.decoder.isEnded) { + this.dispose(); + + return null; + } + + // stream isn't ended so wait for the new + // `data` or `end` event to be received + await new Promise((resolve) => { + this.resolveOnNewEvent = resolve; + }); + } + } +} diff --git a/src/vs/base/common/codecs/baseDecoder.ts b/src/vs/base/common/codecs/baseDecoder.ts new file mode 100644 index 0000000000000..5404e3df0bf42 --- /dev/null +++ b/src/vs/base/common/codecs/baseDecoder.ts @@ -0,0 +1,322 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { assert } from '../assert.js'; +import { Emitter } from '../event.js'; +import { ReadableStream } from '../stream.js'; +import { AsyncDecoder } from './asyncDecoder.js'; +import { Disposable, IDisposable } from '../lifecycle.js'; + +/** + * Event names of {@link ReadableStream} stream. + */ +export type TStreamListenerNames = 'data' | 'error' | 'end'; + +/** + * Base decoder class that can be used to convert stream messages data type + * from one type to another. For instance, a stream of binary data can be + * "decoded" into a stream of well defined objects. + * Intended to be a part of "codec" implementation rather than used directly. + */ +export abstract class BaseDecoder< + T extends NonNullable, + K extends NonNullable = NonNullable, +> extends Disposable implements ReadableStream { + /** + * Flag that indicates if the decoder stream has ended. + */ + protected ended = false; + + protected readonly _onData = this._register(new Emitter()); + protected readonly _onEnd = this._register(new Emitter()); + protected readonly _onError = this._register(new Emitter()); + + /** + * A store of currently registered event listeners. + */ + private readonly _listeners: Map> = new Map(); + + /** + * @param stream The input stream to decode. + */ + constructor( + protected readonly stream: ReadableStream, + ) { + super(); + + this.tryOnStreamData = this.tryOnStreamData.bind(this); + this.onStreamError = this.onStreamError.bind(this); + this.onStreamEnd = this.onStreamEnd.bind(this); + } + + /** + * This method is called when a new incomming data + * is received from the input stream. + */ + protected abstract onStreamData(data: K): void; + + /** + * Start receiveing data from the stream. + * @throws if the decoder stream has already ended. + */ + public start(): this { + assert( + !this.ended, + 'Cannot start stream that has already ended.', + ); + + this.stream.on('data', this.tryOnStreamData); + this.stream.on('error', this.onStreamError); + this.stream.on('end', this.onStreamEnd); + + // this allows to compose decoders together, - if a decoder + // instance is passed as a readble stream to this decoder, + // then we need to call `start` on it too + if (this.stream instanceof BaseDecoder) { + this.stream.start(); + } + + return this; + } + + /** + * Check if the decoder has been ended hence has + * no more data to produce. + */ + public get isEnded(): boolean { + return this.ended; + } + + /** + * Automatically catch and dispatch errors thrown inside `onStreamData`. + */ + private tryOnStreamData(data: K): void { + try { + this.onStreamData(data); + } catch (error) { + this.onStreamError(error); + } + } + + public on(event: 'data', callback: (data: T) => void): void; + public on(event: 'error', callback: (err: Error) => void): void; + public on(event: 'end', callback: () => void): void; + public on(event: TStreamListenerNames, callback: unknown): void { + if (event === 'data') { + return this.onData(callback as (data: T) => void); + } + + if (event === 'error') { + return this.onError(callback as (error: Error) => void); + } + + if (event === 'end') { + return this.onEnd(callback as () => void); + } + + throw new Error(`Invalid event name: ${event}`); + } + + /** + * Add listener for the `data` event. + * @throws if the decoder stream has already ended. + */ + public onData(callback: (data: T) => void): void { + assert( + !this.ended, + 'Cannot subscribe to the `data` event because the decoder stream has already ended.', + ); + + let currentListeners = this._listeners.get('data'); + + if (!currentListeners) { + currentListeners = new Map(); + this._listeners.set('data', currentListeners); + } + + currentListeners.set(callback, this._onData.event(callback)); + } + + /** + * Add listener for the `error` event. + * @throws if the decoder stream has already ended. + */ + public onError(callback: (error: Error) => void): void { + assert( + !this.ended, + 'Cannot subscribe to the `error` event because the decoder stream has already ended.', + ); + + let currentListeners = this._listeners.get('error'); + + if (!currentListeners) { + currentListeners = new Map(); + this._listeners.set('error', currentListeners); + } + + currentListeners.set(callback, this._onError.event(callback)); + } + + /** + * Add listener for the `end` event. + * @throws if the decoder stream has already ended. + */ + public onEnd(callback: () => void): void { + assert( + !this.ended, + 'Cannot subscribe to the `end` event because the decoder stream has already ended.', + ); + + let currentListeners = this._listeners.get('end'); + + if (!currentListeners) { + currentListeners = new Map(); + this._listeners.set('end', currentListeners); + } + + currentListeners.set(callback, this._onEnd.event(callback)); + } + + /** + * Remove all existing event listeners. + */ + public removeAllListeners(): void { + // remove listeners set up by this class + this.stream.removeListener('data', this.tryOnStreamData); + this.stream.removeListener('error', this.onStreamError); + this.stream.removeListener('end', this.onStreamEnd); + + // remove listeners set up by external consumers + for (const [name, listeners] of this._listeners.entries()) { + this._listeners.delete(name); + for (const [listener, disposable] of listeners) { + disposable.dispose(); + listeners.delete(listener); + } + } + } + + /** + * Pauses the stream. + */ + public pause(): void { + this.stream.pause(); + } + + /** + * Resumes the stream if it has been paused. + * @throws if the decoder stream has already ended. + */ + public resume(): void { + assert( + !this.ended, + 'Cannot resume the stream because it has already ended.', + ); + + this.stream.resume(); + } + + /** + * Destroys(disposes) the stream. + */ + public destroy(): void { + this.dispose(); + } + + /** + * Removes a priorly-registered event listener for a specified event. + * + * Note! + * - the callback function must be the same as the one that was used when + * registering the event listener as it is used as an identifier to + * remove the listener + * - this method is idempotent and results in no-op if the listener is + * not found, therefore passing incorrect `callback` function may + * result in silent unexpected behaviour + */ + public removeListener(event: string, callback: Function): void { + for (const [nameName, listeners] of this._listeners.entries()) { + if (nameName !== event) { + continue; + } + + for (const [listener, disposable] of listeners) { + if (listener !== callback) { + continue; + } + + disposable.dispose(); + listeners.delete(listener); + } + } + } + + /** + * This method is called when the input stream ends. + */ + protected onStreamEnd(): void { + if (this.ended) { + return; + } + + this.ended = true; + this._onEnd.fire(); + } + + /** + * This method is called when the input stream emits an error. + * We re-emit the error here by default, but subclasses can + * override this method to handle the error differently. + */ + protected onStreamError(error: Error): void { + this._onError.fire(error); + } + + /** + * Consume all messages from the stream, blocking until the stream finishes. + * @throws if the decoder stream has already ended. + */ + public async consumeAll(): Promise { + assert( + !this.ended, + 'Cannot consume all messages of the stream that has already ended.', + ); + + const messages = []; + + for await (const maybeMessage of this) { + if (maybeMessage === null) { + break; + } + + messages.push(maybeMessage); + } + + return messages; + } + + /** + * Async iterator interface for the decoder. + * @throws if the decoder stream has already ended. + */ + [Symbol.asyncIterator](): AsyncIterator { + assert( + !this.ended, + 'Cannot iterate on messages of the stream that has already ended.', + ); + + const asyncDecoder = this._register(new AsyncDecoder(this)); + + return asyncDecoder[Symbol.asyncIterator](); + } + + public override dispose(): void { + this.onStreamEnd(); + + this.stream.destroy(); + this.removeAllListeners(); + super.dispose(); + } +} diff --git a/src/vs/base/common/codecs/types/ICodec.d.ts b/src/vs/base/common/codecs/types/ICodec.d.ts new file mode 100644 index 0000000000000..cd8abb3ff6744 --- /dev/null +++ b/src/vs/base/common/codecs/types/ICodec.d.ts @@ -0,0 +1,22 @@ +import { ReadableStream } from '../../stream.js'; + +/** + * A codec is an object capable of encoding/decoding a stream of data transforming its messages. + * Useful for abstracting a data transfer or protocol logic on top of a stream of bytes. + * + * For instance, if protocol messages need to be trasferred over `TCP` connection, a codec that + * encodes the messages into a sequence of bytes before sending it to a network socket. Likewise, + * on the other end of the connection, the same codec can decode the sequence of bytes back into + * a sequence of the protocol messages. + */ +export interface ICodec { + /** + * Encode a stream of `K`s into a stream of `T`s. + */ + encode: (value: ReadableStream) => ReadableStream; + + /** + * Decode a stream of `T`s into a stream of `K`s. + */ + decode: (value: ReadableStream) => ReadableStream; +} diff --git a/src/vs/base/common/numbers.ts b/src/vs/base/common/numbers.ts index ab4c9f92e0629..594fcf63545db 100644 --- a/src/vs/base/common/numbers.ts +++ b/src/vs/base/common/numbers.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { assert } from './assert.js'; + export function clamp(value: number, min: number, max: number): number { return Math.min(Math.max(value, min), max); } @@ -96,3 +98,63 @@ export function isPointWithinTriangle( return u >= 0 && v >= 0 && u + v < 1; } + +/** + * Function to get a (pseudo)random integer from a provided `max`...[`min`] range. + * Both `min` and `max` values are inclusive. The `min` value is optional and defaults + * to `0` if not explicitely specified. + * + * @throws in the next cases: + * - if provided `min` or `max` is not a number + * - if provided `min` or `max` is not finite + * - if provided `min` is larger than `max` value + * + * ## Examples + * + * Specifying a `max` value only uses `0` as the `min` value by default: + * + * ```typescript + * // get a random integer between 0 and 10 + * const randomInt = randomInt(10); + * + * assert( + * randomInt >= 0, + * 'Should be greater than or equal to 0.', + * ); + * + * assert( + * randomInt <= 10, + * 'Should be less than or equal to 10.', + * ); + * ``` + * * Specifying both `max` and `min` values: + * + * ```typescript + * // get a random integer between 5 and 8 + * const randomInt = randomInt(8, 5); + * + * assert( + * randomInt >= 5, + * 'Should be greater than or equal to 5.', + * ); + * + * assert( + * randomInt <= 8, + * 'Should be less than or equal to 8.', + * ); + * ``` + */ +export const randomInt = (max: number, min: number = 0): number => { + assert(!isNaN(min), '"min" param is not a number.'); + assert(!isNaN(max), '"max" param is not a number.'); + + assert(isFinite(max), '"max" param is not finite.'); + assert(isFinite(min), '"min" param is not finite.'); + + assert(max > min, `"max"(${max}) param should be greater than "min"(${min}).`); + + const delta = max - min; + const randomFloat = delta * Math.random(); + + return Math.round(min + randomFloat); +}; diff --git a/src/vs/base/common/stream.ts b/src/vs/base/common/stream.ts index 7d990dfcab84f..990c8f1c6bc6a 100644 --- a/src/vs/base/common/stream.ts +++ b/src/vs/base/common/stream.ts @@ -186,7 +186,7 @@ export interface ITransformer { error?: IErrorTransformer; } -export function newWriteableStream(reducer: IReducer, options?: WriteableStreamOptions): WriteableStream { +export function newWriteableStream(reducer: IReducer | null, options?: WriteableStreamOptions): WriteableStream { return new WriteableStreamImpl(reducer, options); } @@ -221,7 +221,13 @@ class WriteableStreamImpl implements WriteableStream { private readonly pendingWritePromises: Function[] = []; - constructor(private reducer: IReducer, private options?: WriteableStreamOptions) { } + /** + * @param reducer a function that reduces the buffered data into a single object; + * because some objects can be complex and non-reducible, we also + * allow passing the explicit `null` value to skip the reduce step + * @param options stream options + */ + constructor(private reducer: IReducer | null, private options?: WriteableStreamOptions) { } pause(): void { if (this.state.destroyed) { @@ -396,18 +402,30 @@ class WriteableStreamImpl implements WriteableStream { } private flowData(): void { - if (this.buffer.data.length > 0) { + // if buffer is empty, nothing to do + if (this.buffer.data.length === 0) { + return; + } + + // if buffer data can be reduced into a single object, + // emit the reduced data + if (typeof this.reducer === 'function') { const fullDataBuffer = this.reducer(this.buffer.data); this.emitData(fullDataBuffer); + } else { + // otherwise emit each buffered data instance individually + for (const data of this.buffer.data) { + this.emitData(data); + } + } - this.buffer.data.length = 0; + this.buffer.data.length = 0; - // When the buffer is empty, resolve all pending writers - const pendingWritePromises = [...this.pendingWritePromises]; - this.pendingWritePromises.length = 0; - pendingWritePromises.forEach(pendingWritePromise => pendingWritePromise()); - } + // when the buffer is empty, resolve all pending writers + const pendingWritePromises = [...this.pendingWritePromises]; + this.pendingWritePromises.length = 0; + pendingWritePromises.forEach(pendingWritePromise => pendingWritePromise()); } private flowErrors(): void { diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 6ad28eaece005..54edfafe71fe5 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { assert } from './assert.js'; + /** * @returns whether the provided parameter is a JavaScript String or not. */ @@ -93,15 +95,52 @@ export function assertType(condition: unknown, type?: string): asserts condition /** * Asserts that the argument passed in is neither undefined nor null. + * + * @see {@link assertDefined} for a similar utility that leverages TS assertion functions to narrow down the type of `arg` to be non-nullable. */ -export function assertIsDefined(arg: T | null | undefined): T { - if (isUndefinedOrNull(arg)) { - throw new Error('Assertion Failed: argument is undefined or null'); - } +export function assertIsDefined(arg: T | null | undefined): NonNullable { + assert( + arg !== null && arg !== undefined, + 'Argument is `undefined` or `null`.', + ); return arg; } +/** + * Asserts that a provided `value` is `defined` - not `null` or `undefined`, + * throwing an error with the provided error or error message, while also + * narrowing down the type of the `value` to be `NonNullable` using TS + * assertion functions. + * + * @throws if the provided `value` is `null` or `undefined`. + * + * ## Examples + * + * ```typescript + * // an assert with an error message + * assertDefined('some value', 'String constant is not defined o_O.'); + * + * // `throws!` the provided error + * assertDefined(null, new Error('Should throw this error.')); + * + * // narrows down the type of `someValue` to be non-nullable + * const someValue: string | undefined | null = blackbox(); + * assertDefined(someValue, 'Some value must be defined.'); + * console.log(someValue.length); // now type of `someValue` is `string` + * ``` + * + * @see {@link assertIsDefined} for a similar utility but without assertion. + * @see {@link https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions typescript-3-7.html#assertion-functions} + */ +export function assertDefined(value: T, error: string | NonNullable): asserts value is NonNullable { + if (value === null || value === undefined) { + const errorToThrow = typeof error === 'string' ? new Error(error) : error; + + throw errorToThrow; + } +} + /** * Asserts that each argument passed in is neither undefined nor null. */ diff --git a/src/vs/base/test/common/numbers.test.ts b/src/vs/base/test/common/numbers.test.ts index ac544e76dfb79..7916bf6710d7e 100644 --- a/src/vs/base/test/common/numbers.test.ts +++ b/src/vs/base/test/common/numbers.test.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; -import { isPointWithinTriangle } from '../../common/numbers.js'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; +import { isPointWithinTriangle, randomInt } from '../../common/numbers.js'; suite('isPointWithinTriangle', () => { ensureNoDisposablesAreLeakedInTestSuite(); @@ -25,3 +25,176 @@ suite('isPointWithinTriangle', () => { assert.ok(result); }); }); + +suite('randomInt', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + /** + * Test helper that allows to run a test on the `randomInt()` + * utility with specified `max` and `min` values. + */ + const testRandomIntUtil = (max: number, min: number | undefined, testName: string) => { + suite(testName, () => { + let i = 0; + while (++i < 5) { + test(`should generate random boolean attempt#${i}`, async () => { + let iterations = 100; + while (iterations-- > 0) { + const int = randomInt(max, min); + + assert( + int <= max, + `Expected ${int} to be less than or equal to ${max}.` + ); + assert( + int >= (min ?? 0), + `Expected ${int} to be greater than or equal to ${min ?? 0}.`, + ); + } + }); + } + + test(`should include min and max`, async () => { + let iterations = 100; + const results = []; + while (iterations-- > 0) { + results.push(randomInt(max, min)); + } + + assert( + results.includes(max), + `Expected ${results} to include ${max}.`, + ); + assert( + results.includes(min ?? 0), + `Expected ${results} to include ${min ?? 0}.`, + ); + }); + }); + }; + + suite('positive numbers', () => { + testRandomIntUtil(5, 2, 'max: 5, min: 2'); + testRandomIntUtil(5, 0, 'max: 5, min: 0'); + testRandomIntUtil(5, undefined, 'max: 5, min: undefined'); + testRandomIntUtil(1, 0, 'max: 0, min: 0'); + }); + + suite('negative numbers', () => { + testRandomIntUtil(-2, -5, 'max: -2, min: -5'); + testRandomIntUtil(0, -5, 'max: 0, min: -5'); + testRandomIntUtil(0, -1, 'max: 0, min: -1'); + }); + + suite('split numbers', () => { + testRandomIntUtil(3, -1, 'max: 3, min: -1'); + testRandomIntUtil(2, -2, 'max: 2, min: -2'); + testRandomIntUtil(1, -3, 'max: 2, min: -2'); + }); + + suite('errors', () => { + test('should throw if "min" is == "max" #1', () => { + assert.throws(() => { + randomInt(200, 200); + }, `"max"(200) param should be greater than "min"(200)."`); + }); + + test('should throw if "min" is == "max" #2', () => { + assert.throws(() => { + randomInt(2, 2); + }, `"max"(2) param should be greater than "min"(2)."`); + }); + + test('should throw if "min" is == "max" #3', () => { + assert.throws(() => { + randomInt(0); + }, `"max"(0) param should be greater than "min"(0)."`); + }); + + test('should throw if "min" is > "max" #1', () => { + assert.throws(() => { + randomInt(2, 3); + }, `"max"(2) param should be greater than "min"(3)."`); + }); + + test('should throw if "min" is > "max" #2', () => { + assert.throws(() => { + randomInt(999, 2000); + }, `"max"(999) param should be greater than "min"(2000)."`); + }); + + test('should throw if "min" is > "max" #3', () => { + assert.throws(() => { + randomInt(0, 1); + }, `"max"(0) param should be greater than "min"(1)."`); + }); + + test('should throw if "min" is > "max" #4', () => { + assert.throws(() => { + randomInt(-5, 2); + }, `"max"(-5) param should be greater than "min"(2)."`); + }); + + test('should throw if "min" is > "max" #5', () => { + assert.throws(() => { + randomInt(-5, 0); + }, `"max"(-5) param should be greater than "min"(0)."`); + }); + + test('should throw if "min" is > "max" #6', () => { + assert.throws(() => { + randomInt(-5); + }, `"max"(-5) param should be greater than "min"(0)."`); + }); + + test('should throw if "max" is `NaN`', () => { + assert.throws(() => { + randomInt(NaN); + }, `"max" param is not a number."`); + }); + + test('should throw if "min" is `NaN`', () => { + assert.throws(() => { + randomInt(5, NaN); + }, `"min" param is not a number."`); + }); + + suite('infinite arguments', () => { + test('should throw if "max" is infinite [Infinity]', () => { + assert.throws(() => { + randomInt(Infinity); + }, `"max" param is not finite."`); + }); + + test('should throw if "max" is infinite [-Infinity]', () => { + assert.throws(() => { + randomInt(-Infinity); + }, `"max" param is not finite."`); + }); + + test('should throw if "max" is infinite [+Infinity]', () => { + assert.throws(() => { + randomInt(+Infinity); + }, `"max" param is not finite."`); + }); + + test('should throw if "min" is infinite [Infinity]', () => { + assert.throws(() => { + randomInt(Infinity, Infinity); + }, `"max" param is not finite."`); + }); + + test('should throw if "min" is infinite [-Infinity]', () => { + assert.throws(() => { + randomInt(Infinity, -Infinity); + }, `"max" param is not finite."`); + }); + + test('should throw if "min" is infinite [+Infinity]', () => { + assert.throws(() => { + randomInt(Infinity, +Infinity); + }, `"max" param is not finite."`); + }); + }); + }); +}); diff --git a/src/vs/base/test/common/stream.test.ts b/src/vs/base/test/common/stream.test.ts index b25769c24153a..83e81e93aaece 100644 --- a/src/vs/base/test/common/stream.test.ts +++ b/src/vs/base/test/common/stream.test.ts @@ -5,10 +5,10 @@ import assert from 'assert'; import { timeout } from '../../common/async.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; import { bufferToReadable, VSBuffer } from '../../common/buffer.js'; import { CancellationTokenSource } from '../../common/cancellation.js'; import { consumeReadable, consumeStream, isReadable, isReadableBufferedStream, isReadableStream, listenStream, newWriteableStream, peekReadable, peekStream, prefixedReadable, prefixedStream, Readable, ReadableStream, toReadable, toStream, transform } from '../../common/stream.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; suite('Stream', () => { @@ -91,6 +91,104 @@ suite('Stream', () => { assert.strictEqual(chunks.length, 4); }); + test('stream with non-reducible messages', () => { + /** + * A complex object that cannot be reduced to a single object. + */ + class TestMessage { + constructor(public value: string) { } + } + + const stream = newWriteableStream(null); + + let error = false; + stream.on('error', e => { + error = true; + }); + + let end = false; + stream.on('end', () => { + end = true; + }); + + stream.write(new TestMessage('Hello')); + + const chunks: TestMessage[] = []; + stream.on('data', data => { + chunks.push(data); + }); + + assert( + chunks[0] instanceof TestMessage, + 'Message `0` must be an instance of `TestMessage`.', + ); + assert.strictEqual(chunks[0].value, 'Hello'); + + stream.write(new TestMessage('World')); + + assert( + chunks[1] instanceof TestMessage, + 'Message `1` must be an instance of `TestMessage`.', + ); + assert.strictEqual(chunks[1].value, 'World'); + + assert.strictEqual(error, false); + assert.strictEqual(end, false); + + stream.pause(); + stream.write(new TestMessage('1')); + stream.write(new TestMessage('2')); + stream.write(new TestMessage('3')); + + assert.strictEqual(chunks.length, 2); + + stream.resume(); + + assert.strictEqual(chunks.length, 5); + + assert( + chunks[2] instanceof TestMessage, + 'Message `2` must be an instance of `TestMessage`.', + ); + assert.strictEqual(chunks[2].value, '1'); + + assert( + chunks[3] instanceof TestMessage, + 'Message `3` must be an instance of `TestMessage`.', + ); + assert.strictEqual(chunks[3].value, '2'); + + assert( + chunks[4] instanceof TestMessage, + 'Message `4` must be an instance of `TestMessage`.', + ); + assert.strictEqual(chunks[4].value, '3'); + + stream.error(new Error()); + assert.strictEqual(error, true); + + error = false; + stream.error(new Error()); + assert.strictEqual(error, true); + + stream.end(new TestMessage('Final Bit')); + assert.strictEqual(chunks.length, 6); + + assert( + chunks[5] instanceof TestMessage, + 'Message `5` must be an instance of `TestMessage`.', + ); + assert.strictEqual(chunks[5].value, 'Final Bit'); + + + assert.strictEqual(end, true); + + stream.destroy(); + + stream.write(new TestMessage('Unexpected')); + assert.strictEqual(chunks.length, 6); + }); + test('WriteableStream - end with empty string works', async () => { const reducer = (strings: string[]) => strings.length > 0 ? strings.join() : 'error'; const stream = newWriteableStream(reducer); diff --git a/src/vs/base/test/common/types.test.ts b/src/vs/base/test/common/types.test.ts index 8f6eba0d0f32a..da00553920108 100644 --- a/src/vs/base/test/common/types.test.ts +++ b/src/vs/base/test/common/types.test.ts @@ -6,6 +6,7 @@ import assert from 'assert'; import * as types from '../../common/types.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; +import { assertDefined } from '../../common/types.js'; suite('Types', () => { @@ -174,6 +175,135 @@ suite('Types', () => { assert.strictEqual(res[2], 'Hello'); }); + suite('assertDefined', () => { + test('should not throw if `value` is defined (bool)', async () => { + assert.doesNotThrow(function () { + assertDefined(true, 'Oops something happened.'); + }); + }); + + test('should not throw if `value` is defined (number)', async () => { + assert.doesNotThrow(function () { + assertDefined(5, 'Oops something happened.'); + }); + }); + + test('should not throw if `value` is defined (zero)', async () => { + assert.doesNotThrow(function () { + assertDefined(0, 'Oops something happened.'); + }); + }); + + test('should not throw if `value` is defined (string)', async () => { + assert.doesNotThrow(function () { + assertDefined('some string', 'Oops something happened.'); + }); + }); + + test('should not throw if `value` is defined (empty string)', async () => { + assert.doesNotThrow(function () { + assertDefined('', 'Oops something happened.'); + }); + }); + + /** + * Note! API of `assert.throws()` is different in the browser + * and in Node.js, and it is not possible to use the same code + * here. Therefore we had to resort to the manual try/catch. + */ + const assertThrows = ( + testFunction: () => void, + errorMessage: string, + ) => { + let thrownError: Error | undefined; + + try { + testFunction(); + } catch (e) { + thrownError = e as Error; + } + + assertDefined(thrownError, 'Must throw an error.'); + assert( + thrownError instanceof Error, + 'Error must be an instance of `Error`.', + ); + + assert.strictEqual( + thrownError.message, + errorMessage, + 'Error must have correct message.', + ); + }; + + test('should throw if `value` is `null`', async () => { + const errorMessage = 'Uggh ohh!'; + assertThrows(() => { + assertDefined(null, errorMessage); + }, errorMessage); + }); + + test('should throw if `value` is `undefined`', async () => { + const errorMessage = 'Oh no!'; + assertThrows(() => { + assertDefined(undefined, new Error(errorMessage)); + }, errorMessage); + }); + + test('should throw assertion error by default', async () => { + const errorMessage = 'Uggh ohh!'; + let thrownError: Error | undefined; + try { + assertDefined(null, errorMessage); + } catch (e) { + thrownError = e as Error; + } + + assertDefined(thrownError, 'Must throw an error.'); + + assert( + thrownError instanceof Error, + 'Error must be an instance of `Error`.', + ); + + assert.strictEqual( + thrownError.message, + errorMessage, + 'Error must have correct message.', + ); + }); + + test('should throw provided error instance', async () => { + class TestError extends Error { + constructor(...args: ConstructorParameters) { + super(...args); + + this.name = 'TestError'; + } + } + + const errorMessage = 'Oops something hapenned.'; + const error = new TestError(errorMessage); + + let thrownError; + try { + assertDefined(null, error); + } catch (e) { + thrownError = e; + } + + assert( + thrownError instanceof TestError, + 'Error must be an instance of `TestError`.', + ); + assert.strictEqual( + thrownError.message, + errorMessage, + 'Error must have correct message.', + ); + }); + }); + test('validateConstraints', () => { types.validateConstraints([1, 'test', true], [Number, String, Boolean]); types.validateConstraints([1, 'test', true], ['number', 'string', 'boolean']); diff --git a/src/vs/editor/common/codecs/baseToken.ts b/src/vs/editor/common/codecs/baseToken.ts new file mode 100644 index 0000000000000..9ebe3ad8abc3c --- /dev/null +++ b/src/vs/editor/common/codecs/baseToken.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IRange, Range } from '../../../editor/common/core/range.js'; + +/** + * Base class for all tokens with a `range` that + * reflects token position in the original data. + */ +export abstract class BaseToken { + constructor( + private _range: Range, + ) { } + + public get range(): Range { + return this._range; + } + + /** + * Check if this token has the same range as another one. + */ + public sameRange(other: Range): boolean { + return this.range.equalsRange(other); + } + + /** + * Returns a string representation of the token. + */ + public abstract toString(): string; + + /** + * Check if this token is equal to another one. + */ + public equals(other: T): boolean { + if (!(other instanceof this.constructor)) { + return false; + } + + return this.sameRange(other.range); + } + + /** + * Change `range` of the token with provided range components. + */ + public withRange(components: Partial): this { + this._range = new Range( + components.startLineNumber ?? this.range.startLineNumber, + components.startColumn ?? this.range.startColumn, + components.endLineNumber ?? this.range.endLineNumber, + components.endColumn ?? this.range.endColumn, + ); + + return this; + } +} diff --git a/src/vs/editor/common/codecs/linesCodec/linesDecoder.ts b/src/vs/editor/common/codecs/linesCodec/linesDecoder.ts new file mode 100644 index 0000000000000..3bd72e5bd7363 --- /dev/null +++ b/src/vs/editor/common/codecs/linesCodec/linesDecoder.ts @@ -0,0 +1,213 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Line } from './tokens/line.js'; +import { Range } from '../../core/range.js'; +import { NewLine } from './tokens/newLine.js'; +import { assert } from '../../../../base/common/assert.js'; +import { CarriageReturn } from './tokens/carriageReturn.js'; +import { VSBuffer } from '../../../../base/common/buffer.js'; +import { assertDefined } from '../../../../base/common/types.js'; +import { BaseDecoder } from '../../../../base/common/codecs/baseDecoder.js'; + +/** + * Tokens produced by the `LinesDecoder`. + */ +export type TLineToken = Line | CarriageReturn | NewLine; + +/** + * The `decoder` part of the `LinesCodec` and is able to transform + * data from a binary stream into a stream of text lines(`Line`). + */ +export class LinesDecoder extends BaseDecoder { + /** + * Buffered received data yet to be processed. + */ + private buffer: VSBuffer = VSBuffer.alloc(0); + + /** + * The last emitted `Line` token, if any. The value is used + * to correctly emit remaining line range in the `onStreamEnd` + * method when underlying input stream ends and `buffer` still + * contains some data that must be emitted as the last line. + */ + private lastEmittedLine?: Line; + + /** + * Process data received from the input stream. + */ + protected override onStreamData(chunk: VSBuffer): void { + this.buffer = VSBuffer.concat([this.buffer, chunk]); + + this.processData(false); + } + + /** + * Process buffered data. + * + * @param streamEnded Flag that indicates if the input stream has ended, + * which means that is the last call of this method. + * @throws If internal logic implementation error is detected. + */ + private processData( + streamEnded: boolean, + ) { + // iterate over each line of the data buffer, emitting each line + // as a `Line` token followed by a `NewLine` token, if applies + while (this.buffer.byteLength > 0) { + // get line number based on a previously emitted line, if any + const lineNumber = this.lastEmittedLine + ? this.lastEmittedLine.range.startLineNumber + 1 + : 1; + + // find the `\r`, `\n`, or `\r\n` tokens in the data + const endOfLineTokens = this.findEndOfLineTokens(lineNumber); + const firstToken = endOfLineTokens[0]; + + // if no end-of-the-line tokens found, stop processing because we + // either (1)need more data to arraive or (2)the stream has ended + // in the case (2) remaining data must be emitted as the last line + if (!firstToken) { + // (2) if `streamEnded`, we need to emit the whole remaining + // data as the last line immediately + if (streamEnded) { + this.emitLine(lineNumber, this.buffer.slice(0)); + } + + break; + } + + // emit the line found in the data as the `Line` token + this.emitLine(lineNumber, this.buffer.slice(0, firstToken.range.startColumn - 1)); + + // must always hold true as the `emitLine` above sets this + assertDefined( + this.lastEmittedLine, + 'No last emitted line found.', + ); + + // emit the end-of-the-line tokens + let startColumn = this.lastEmittedLine.range.endColumn; + for (const token of endOfLineTokens) { + const endColumn = startColumn + token.byte.byteLength; + // emit the token updating its column start/end numbers based on + // the emitted line text length and previous end-of-the-line token + this._onData.fire(token.withRange({ startColumn, endColumn })); + // shorten the data buffer by the length of the token + this.buffer = this.buffer.slice(token.byte.byteLength); + // update the start column for the next token + startColumn = endColumn; + } + } + + // if the stream has ended, assert that the input data buffer is now empty + // otherwise we have a logic error and leaving some buffered data behind + if (streamEnded) { + assert( + this.buffer.byteLength === 0, + 'Expected the input data buffer to be empty when the stream ends.', + ); + } + } + + /** + * Find the end of line tokens in the data buffer. + * Can return: + * - [`\r`, `\n`] tokens if the sequence is found + * - [`\r`] token if only the carriage return is found + * - [`\n`] token if only the newline is found + * - an `empty array` if no end of line tokens found + */ + private findEndOfLineTokens( + lineNumber: number, + ): (CarriageReturn | NewLine)[] { + const result = []; + + // find the first occurrence of the carriage return and newline tokens + const carriageReturnIndex = this.buffer.indexOf(CarriageReturn.byte); + const newLineIndex = this.buffer.indexOf(NewLine.byte); + + // if the `\r` comes before the `\n`(if `\n` present at all) + if (carriageReturnIndex >= 0 && (carriageReturnIndex < newLineIndex || newLineIndex === -1)) { + // add the carriage return token first + result.push( + new CarriageReturn(new Range( + lineNumber, + (carriageReturnIndex + 1), + lineNumber, + (carriageReturnIndex + 1) + CarriageReturn.byte.byteLength, + )), + ); + + // if the `\r\n` sequence + if (newLineIndex === carriageReturnIndex + 1) { + // add the newline token to the result + result.push( + new NewLine(new Range( + lineNumber, + (newLineIndex + 1), + lineNumber, + (newLineIndex + 1) + NewLine.byte.byteLength, + )), + ); + } + + if (this.buffer.byteLength > carriageReturnIndex + 1) { + // either `\r` or `\r\n` cases found + return result; + } + + return []; + } + + // no `\r`, but there is `\n` + if (newLineIndex >= 0) { + result.push( + new NewLine(new Range( + lineNumber, + (newLineIndex + 1), + lineNumber, + (newLineIndex + 1) + NewLine.byte.byteLength, + )), + ); + } + + // neither `\r` nor `\n` found, no end of line found at all + return result; + } + + /** + * Emit a provided line as the `Line` token to the output stream. + */ + private emitLine( + lineNumber: number, // Note! 1-based indexing + lineBytes: VSBuffer, + ): void { + + const line = new Line(lineNumber, lineBytes.toString()); + this._onData.fire(line); + + // store the last emitted line so we can use it when we need + // to send the remaining line in the `onStreamEnd` method + this.lastEmittedLine = line; + + // shorten the data buffer by the length of the line emitted + this.buffer = this.buffer.slice(lineBytes.byteLength); + } + + /** + * Handle the end of the input stream - if the buffer still has some data, + * emit it as the last available line token before firing the `onEnd` event. + */ + protected override onStreamEnd(): void { + // if the input data buffer is not empty when the input stream ends, emit + // the remaining data as the last line before firing the `onEnd` event + if (this.buffer.byteLength > 0) { + this.processData(true); + } + + super.onStreamEnd(); + } +} diff --git a/src/vs/editor/common/codecs/linesCodec/tokens/carriageReturn.ts b/src/vs/editor/common/codecs/linesCodec/tokens/carriageReturn.ts new file mode 100644 index 0000000000000..5120f4ac322b9 --- /dev/null +++ b/src/vs/editor/common/codecs/linesCodec/tokens/carriageReturn.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Line } from './line.js'; +import { BaseToken } from '../../baseToken.js'; +import { Range } from '../../../core/range.js'; +import { Position } from '../../../core/position.js'; +import { VSBuffer } from '../../../../../base/common/buffer.js'; + +/** + * Token that represent a `carriage return` with a `range`. The `range` + * value reflects the position of the token in the original data. + */ +export class CarriageReturn extends BaseToken { + /** + * The underlying symbol of the token. + */ + public static readonly symbol: string = '\r'; + + /** + * The byte representation of the {@link symbol}. + */ + public static readonly byte = VSBuffer.fromString(CarriageReturn.symbol); + + /** + * The byte representation of the token. + */ + public get byte() { + return CarriageReturn.byte; + } + + /** + * Create new `CarriageReturn` token with range inside + * the given `Line` at the given `column number`. + */ + public static newOnLine( + line: Line, + atColumnNumber: number, + ): CarriageReturn { + const { range } = line; + + const startPosition = new Position(range.startLineNumber, atColumnNumber); + const endPosition = new Position(range.startLineNumber, atColumnNumber + this.symbol.length); + + return new CarriageReturn(Range.fromPositions( + startPosition, + endPosition, + )); + } + + /** + * Returns a string representation of the token. + */ + public override toString(): string { + return `carriage-return${this.range}`; + } +} diff --git a/src/vs/editor/common/codecs/linesCodec/tokens/line.ts b/src/vs/editor/common/codecs/linesCodec/tokens/line.ts new file mode 100644 index 0000000000000..6669169967f57 --- /dev/null +++ b/src/vs/editor/common/codecs/linesCodec/tokens/line.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BaseToken } from '../../baseToken.js'; +import { assert } from '../../../../../base/common/assert.js'; +import { Range } from '../../../../../editor/common/core/range.js'; + +/** + * Token representing a line of text with a `range` which + * reflects the line's position in the original data. + */ +export class Line extends BaseToken { + constructor( + // the line index + // Note! 1-based indexing + lineNumber: number, + // the line contents + public readonly text: string, + ) { + assert( + !isNaN(lineNumber), + `The line number must not be a NaN.`, + ); + + assert( + lineNumber > 0, + `The line number must be >= 1, got "${lineNumber}".`, + ); + + super( + new Range( + lineNumber, + 1, + lineNumber, + text.length + 1, + ), + ); + } + + /** + * Check if this token is equal to another one. + */ + public override equals(other: T): boolean { + if (!super.equals(other)) { + return false; + } + + if (!(other instanceof Line)) { + return false; + } + + return this.text === other.text; + } + + /** + * Returns a string representation of the token. + */ + public override toString(): string { + return `line("${this.text}")${this.range}`; + } +} diff --git a/src/vs/editor/common/codecs/linesCodec/tokens/newLine.ts b/src/vs/editor/common/codecs/linesCodec/tokens/newLine.ts new file mode 100644 index 0000000000000..19b80dd88a3c3 --- /dev/null +++ b/src/vs/editor/common/codecs/linesCodec/tokens/newLine.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Line } from './line.js'; +import { BaseToken } from '../../baseToken.js'; +import { VSBuffer } from '../../../../../base/common/buffer.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { Position } from '../../../../../editor/common/core/position.js'; + +/** + * A token that represent a `new line` with a `range`. The `range` + * value reflects the position of the token in the original data. + */ +export class NewLine extends BaseToken { + /** + * The underlying symbol of the `NewLine` token. + */ + public static readonly symbol: string = '\n'; + + /** + * The byte representation of the {@link symbol}. + */ + public static readonly byte = VSBuffer.fromString(NewLine.symbol); + + /** + * The byte representation of the token. + */ + public get byte() { + return NewLine.byte; + } + + /** + * Create new `NewLine` token with range inside + * the given `Line` at the given `column number`. + */ + public static newOnLine( + line: Line, + atColumnNumber: number, + ): NewLine { + const { range } = line; + + const startPosition = new Position(range.startLineNumber, atColumnNumber); + const endPosition = new Position(range.startLineNumber, atColumnNumber + this.symbol.length); + + return new NewLine( + Range.fromPositions(startPosition, endPosition), + ); + } + + /** + * Returns a string representation of the token. + */ + public override toString(): string { + return `newline${this.range}`; + } +} diff --git a/src/vs/editor/common/codecs/simpleCodec/simpleDecoder.ts b/src/vs/editor/common/codecs/simpleCodec/simpleDecoder.ts new file mode 100644 index 0000000000000..64173eceabdaa --- /dev/null +++ b/src/vs/editor/common/codecs/simpleCodec/simpleDecoder.ts @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { FormFeed } from './tokens/formFeed.js'; +import { Tab } from '../simpleCodec/tokens/tab.js'; +import { Word } from '../simpleCodec/tokens/word.js'; +import { VerticalTab } from './tokens/verticalTab.js'; +import { Space } from '../simpleCodec/tokens/space.js'; +import { NewLine } from '../linesCodec/tokens/newLine.js'; +import { VSBuffer } from '../../../../base/common/buffer.js'; +import { ReadableStream } from '../../../../base/common/stream.js'; +import { CarriageReturn } from '../linesCodec/tokens/carriageReturn.js'; +import { LinesDecoder, TLineToken } from '../linesCodec/linesDecoder.js'; +import { BaseDecoder } from '../../../../base/common/codecs/baseDecoder.js'; + +/** + * A token type that this decoder can handle. + */ +export type TSimpleToken = Word | Space | Tab | VerticalTab | NewLine | FormFeed | CarriageReturn; + +/** + * Characters that stop a "word" sequence. + * Note! the `\r` and `\n` are excluded from the list because this decoder based on `LinesDecoder` which + * already handles the `carriagereturn`/`newline` cases and emits lines that don't contain them. + */ +const STOP_CHARACTERS = [Space.symbol, Tab.symbol, VerticalTab.symbol, FormFeed.symbol]; + +/** + * A decoder that can decode a stream of `Line`s into a stream + * of simple token, - `Word`, `Space`, `Tab`, `NewLine`, etc. + */ +export class SimpleDecoder extends BaseDecoder { + constructor( + stream: ReadableStream, + ) { + super(new LinesDecoder(stream)); + } + + protected override onStreamData(token: TLineToken): void { + // re-emit new line tokens + if (token instanceof CarriageReturn || token instanceof NewLine) { + this._onData.fire(token); + + return; + } + + // loop through the text separating it into `Word` and `Space` tokens + let i = 0; + while (i < token.text.length) { + // index is 0-based, but column numbers are 1-based + const columnNumber = i + 1; + + // if a space character, emit a `Space` token and continue + if (token.text[i] === Space.symbol) { + this._onData.fire(Space.newOnLine(token, columnNumber)); + + i++; + continue; + } + + // if a tab character, emit a `Tab` token and continue + if (token.text[i] === Tab.symbol) { + this._onData.fire(Tab.newOnLine(token, columnNumber)); + + i++; + continue; + } + + // if a vertical tab character, emit a `VerticalTab` token and continue + if (token.text[i] === VerticalTab.symbol) { + this._onData.fire(VerticalTab.newOnLine(token, columnNumber)); + + i++; + continue; + } + + // if a form feed character, emit a `FormFeed` token and continue + if (token.text[i] === FormFeed.symbol) { + this._onData.fire(FormFeed.newOnLine(token, columnNumber)); + + i++; + continue; + } + + // if a non-space character, parse out the whole word and + // emit it, then continue from the last word character position + let word = ''; + while (i < token.text.length && !(STOP_CHARACTERS.includes(token.text[i]))) { + word += token.text[i]; + i++; + } + + this._onData.fire( + Word.newOnLine(word, token, columnNumber), + ); + } + } +} diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/formFeed.ts b/src/vs/editor/common/codecs/simpleCodec/tokens/formFeed.ts new file mode 100644 index 0000000000000..ab40192f459e4 --- /dev/null +++ b/src/vs/editor/common/codecs/simpleCodec/tokens/formFeed.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BaseToken } from '../../baseToken.js'; +import { Line } from '../../linesCodec/tokens/line.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { Position } from '../../../../../editor/common/core/position.js'; + +/** + * Token that represent a `form feed` with a `range`. The `range` + * value reflects the position of the token in the original data. + */ +export class FormFeed extends BaseToken { + /** + * The underlying symbol of the token. + */ + public static readonly symbol: string = '\f'; + + /** + * Create new `FormFeed` token with range inside + * the given `Line` at the given `column number`. + */ + public static newOnLine( + line: Line, + atColumnNumber: number, + ): FormFeed { + const { range } = line; + + const startPosition = new Position(range.startLineNumber, atColumnNumber); + const endPosition = new Position(range.startLineNumber, atColumnNumber + this.symbol.length); + + return new FormFeed(Range.fromPositions( + startPosition, + endPosition, + )); + } + + /** + * Returns a string representation of the token. + */ + public override toString(): string { + return `formfeed${this.range}`; + } +} diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/space.ts b/src/vs/editor/common/codecs/simpleCodec/tokens/space.ts new file mode 100644 index 0000000000000..9961c38ece9ce --- /dev/null +++ b/src/vs/editor/common/codecs/simpleCodec/tokens/space.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BaseToken } from '../../baseToken.js'; +import { Line } from '../../linesCodec/tokens/line.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { Position } from '../../../../../editor/common/core/position.js'; + +/** + * A token that represent a `space` with a `range`. The `range` + * value reflects the position of the token in the original data. + */ +export class Space extends BaseToken { + /** + * The underlying symbol of the `Space` token. + */ + public static readonly symbol: string = ' '; + + /** + * Create new `Space` token with range inside + * the given `Line` at the given `column number`. + */ + public static newOnLine( + line: Line, + atColumnNumber: number, + ): Space { + const { range } = line; + + const startPosition = new Position(range.startLineNumber, atColumnNumber); + const endPosition = new Position(range.startLineNumber, atColumnNumber + this.symbol.length); + + return new Space(Range.fromPositions( + startPosition, + endPosition, + )); + } + + /** + * Returns a string representation of the token. + */ + public override toString(): string { + return `space${this.range}`; + } +} diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/tab.ts b/src/vs/editor/common/codecs/simpleCodec/tokens/tab.ts new file mode 100644 index 0000000000000..aab11327bc156 --- /dev/null +++ b/src/vs/editor/common/codecs/simpleCodec/tokens/tab.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BaseToken } from '../../baseToken.js'; +import { Line } from '../../linesCodec/tokens/line.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { Position } from '../../../../../editor/common/core/position.js'; + +/** + * A token that represent a `tab` with a `range`. The `range` + * value reflects the position of the token in the original data. + */ +export class Tab extends BaseToken { + /** + * The underlying symbol of the `Tab` token. + */ + public static readonly symbol: string = '\t'; + + /** + * Create new `Tab` token with range inside + * the given `Line` at the given `column number`. + */ + public static newOnLine( + line: Line, + atColumnNumber: number, + ): Tab { + const { range } = line; + + const startPosition = new Position(range.startLineNumber, atColumnNumber); + // the tab token length is 1, hence `+ 1` + const endPosition = new Position(range.startLineNumber, atColumnNumber + this.symbol.length); + + return new Tab(Range.fromPositions( + startPosition, + endPosition, + )); + } + + /** + * Returns a string representation of the token. + */ + public override toString(): string { + return `tab${this.range}`; + } +} diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/verticalTab.ts b/src/vs/editor/common/codecs/simpleCodec/tokens/verticalTab.ts new file mode 100644 index 0000000000000..11e5ca6efabfa --- /dev/null +++ b/src/vs/editor/common/codecs/simpleCodec/tokens/verticalTab.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BaseToken } from '../../baseToken.js'; +import { Line } from '../../linesCodec/tokens/line.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { Position } from '../../../../../editor/common/core/position.js'; + +/** + * Token that represent a `vertical tab` with a `range`. The `range` + * value reflects the position of the token in the original data. + */ +export class VerticalTab extends BaseToken { + /** + * The underlying symbol of the `VerticalTab` token. + */ + public static readonly symbol: string = '\v'; + + /** + * Create new `VerticalTab` token with range inside + * the given `Line` at the given `column number`. + */ + public static newOnLine( + line: Line, + atColumnNumber: number, + ): VerticalTab { + const { range } = line; + + const startPosition = new Position(range.startLineNumber, atColumnNumber); + const endPosition = new Position(range.startLineNumber, atColumnNumber + this.symbol.length); + + return new VerticalTab(Range.fromPositions( + startPosition, + endPosition, + )); + } + + /** + * Returns a string representation of the token. + */ + public override toString(): string { + return `vtab${this.range}`; + } +} diff --git a/src/vs/editor/common/codecs/simpleCodec/tokens/word.ts b/src/vs/editor/common/codecs/simpleCodec/tokens/word.ts new file mode 100644 index 0000000000000..fc3cefa79be68 --- /dev/null +++ b/src/vs/editor/common/codecs/simpleCodec/tokens/word.ts @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BaseToken } from '../../baseToken.js'; +import { Line } from '../../linesCodec/tokens/line.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { Position } from '../../../../../editor/common/core/position.js'; + +/** + * A token that represent a word - a set of continuous + * characters without stop characters, like a `space`, + * a `tab`, or a `new line`. + */ +export class Word extends BaseToken { + constructor( + /** + * The word range. + */ + range: Range, + + /** + * The string value of the word. + */ + public readonly text: string, + ) { + super(range); + } + + /** + * Create new `Word` token with the given `text` and the range + * inside the given `Line` at the specified `column number`. + */ + public static newOnLine( + text: string, + line: Line, + atColumnNumber: number, + ): Word { + const { range } = line; + + const startPosition = new Position(range.startLineNumber, atColumnNumber); + const endPosition = new Position(range.startLineNumber, atColumnNumber + text.length); + + return new Word( + Range.fromPositions(startPosition, endPosition), + text, + ); + } + + /** + * Check if this token is equal to another one. + */ + public override equals(other: T): boolean { + if (!super.equals(other)) { + return false; + } + + if (!(other instanceof Word)) { + return false; + } + + return this.text === other.text; + } + + /** + * Returns a string representation of the token. + */ + public override toString(): string { + return `word("${this.text.slice(0, 8)}")${this.range}`; + } +} diff --git a/src/vs/editor/test/common/codecs/linesDecoder.test.ts b/src/vs/editor/test/common/codecs/linesDecoder.test.ts new file mode 100644 index 0000000000000..b3e6c91be1335 --- /dev/null +++ b/src/vs/editor/test/common/codecs/linesDecoder.test.ts @@ -0,0 +1,140 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TestDecoder } from '../utils/testDecoder.js'; +import { Range } from '../../../common/core/range.js'; +import { VSBuffer } from '../../../../base/common/buffer.js'; +import { newWriteableStream } from '../../../../base/common/stream.js'; +import { Line } from '../../../common/codecs/linesCodec/tokens/line.js'; +import { NewLine } from '../../../common/codecs/linesCodec/tokens/newLine.js'; +import { CarriageReturn } from '../../../common/codecs/linesCodec/tokens/carriageReturn.js'; +import { LinesDecoder, TLineToken } from '../../../common/codecs/linesCodec/linesDecoder.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; + +/** + * A reusable test utility that asserts that a `LinesDecoder` instance + * correctly decodes `inputData` into a stream of `TLineToken` tokens. + * + * ## Examples + * + * ```typescript + * // create a new test utility instance + * const test = testDisposables.add(new TestLinesDecoder()); + * + * // run the test + * await test.run( + * ' hello world\n', + * [ + * new Line(1, ' hello world'), + * new NewLine(new Range(1, 13, 1, 14)), + * ], + * ); + */ +export class TestLinesDecoder extends TestDecoder { + constructor() { + const stream = newWriteableStream(null); + const decoder = new LinesDecoder(stream); + + super(stream, decoder); + } +} + +suite('LinesDecoder', () => { + suite('produces expected tokens', () => { + const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); + + test('input starts with line data', async () => { + const test = testDisposables.add(new TestLinesDecoder()); + + await test.run( + ' hello world\nhow are you doing?\n\n 😊 \r ', + [ + new Line(1, ' hello world'), + new NewLine(new Range(1, 13, 1, 14)), + new Line(2, 'how are you doing?'), + new NewLine(new Range(2, 19, 2, 20)), + new Line(3, ''), + new NewLine(new Range(3, 1, 3, 2)), + new Line(4, ' 😊 '), + new CarriageReturn(new Range(4, 5, 4, 6)), + new Line(5, ' '), + ], + ); + }); + + test('input starts with a new line', async () => { + const test = testDisposables.add(new TestLinesDecoder()); + + await test.run( + '\nsome text on this line\n\n\nanother 💬 on this line\r\n🤫\n', + [ + new Line(1, ''), + new NewLine(new Range(1, 1, 1, 2)), + new Line(2, 'some text on this line'), + new NewLine(new Range(2, 23, 2, 24)), + new Line(3, ''), + new NewLine(new Range(3, 1, 3, 2)), + new Line(4, ''), + new NewLine(new Range(4, 1, 4, 2)), + new Line(5, 'another 💬 on this line'), + new CarriageReturn(new Range(5, 24, 5, 25)), + new NewLine(new Range(5, 25, 5, 26)), + new Line(6, '🤫'), + new NewLine(new Range(6, 3, 6, 4)), + ], + ); + }); + + test('input starts and ends with multiple new lines', async () => { + const test = testDisposables.add(new TestLinesDecoder()); + + await test.run( + '\n\n\r\nciao! 🗯️\t💭 💥 come\tva?\n\n\n\n\n', + [ + new Line(1, ''), + new NewLine(new Range(1, 1, 1, 2)), + new Line(2, ''), + new NewLine(new Range(2, 1, 2, 2)), + new Line(3, ''), + new CarriageReturn(new Range(3, 1, 3, 2)), + new NewLine(new Range(3, 2, 3, 3)), + new Line(4, 'ciao! 🗯️\t💭 💥 come\tva?'), + new NewLine(new Range(4, 25, 4, 26)), + new Line(5, ''), + new NewLine(new Range(5, 1, 5, 2)), + new Line(6, ''), + new NewLine(new Range(6, 1, 6, 2)), + new Line(7, ''), + new NewLine(new Range(7, 1, 7, 2)), + new Line(8, ''), + new NewLine(new Range(8, 1, 8, 2)), + ], + ); + }); + + test('single carriage return is treated as new line', async () => { + const test = testDisposables.add(new TestLinesDecoder()); + + await test.run( + '\r\rhaalo! 💥💥 how\'re you?\r ?!\r\n\r\n ', + [ + new Line(1, ''), + new CarriageReturn(new Range(1, 1, 1, 2)), + new Line(2, ''), + new CarriageReturn(new Range(2, 1, 2, 2)), + new Line(3, 'haalo! 💥💥 how\'re you?'), + new CarriageReturn(new Range(3, 24, 3, 25)), + new Line(4, ' ?!'), + new CarriageReturn(new Range(4, 4, 4, 5)), + new NewLine(new Range(4, 5, 4, 6)), + new Line(5, ''), + new CarriageReturn(new Range(5, 1, 5, 2)), + new NewLine(new Range(5, 2, 5, 3)), + new Line(6, ' '), + ], + ); + }); + }); +}); diff --git a/src/vs/editor/test/common/codecs/simpleDecoder.test.ts b/src/vs/editor/test/common/codecs/simpleDecoder.test.ts new file mode 100644 index 0000000000000..2e57a1c8219ba --- /dev/null +++ b/src/vs/editor/test/common/codecs/simpleDecoder.test.ts @@ -0,0 +1,111 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TestDecoder } from '../utils/testDecoder.js'; +import { Range } from '../../../common/core/range.js'; +import { VSBuffer } from '../../../../base/common/buffer.js'; +import { newWriteableStream } from '../../../../base/common/stream.js'; +import { Tab } from '../../../common/codecs/simpleCodec/tokens/tab.js'; +import { Word } from '../../../common/codecs/simpleCodec/tokens/word.js'; +import { Space } from '../../../common/codecs/simpleCodec/tokens/space.js'; +import { NewLine } from '../../../common/codecs/linesCodec/tokens/newLine.js'; +import { FormFeed } from '../../../common/codecs/simpleCodec/tokens/formFeed.js'; +import { VerticalTab } from '../../../common/codecs/simpleCodec/tokens/verticalTab.js'; +import { CarriageReturn } from '../../../common/codecs/linesCodec/tokens/carriageReturn.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +import { SimpleDecoder, TSimpleToken } from '../../../common/codecs/simpleCodec/simpleDecoder.js'; + +/** + * A reusable test utility that asserts that a `SimpleDecoder` instance + * correctly decodes `inputData` into a stream of `TSimpleToken` tokens. + * + * ## Examples + * + * ```typescript + * // create a new test utility instance + * const test = testDisposables.add(new TestSimpleDecoder()); + * + * // run the test + * await test.run( + * ' hello world\n', + * [ + * new Space(new Range(1, 1, 1, 2)), + * new Word(new Range(1, 2, 1, 7), 'hello'), + * new Space(new Range(1, 7, 1, 8)), + * new Word(new Range(1, 8, 1, 13), 'world'), + * new NewLine(new Range(1, 13, 1, 14)), + * ], + * ); + */ +export class TestSimpleDecoder extends TestDecoder { + constructor() { + const stream = newWriteableStream(null); + const decoder = new SimpleDecoder(stream); + + super(stream, decoder); + } +} + +suite('SimpleDecoder', () => { + const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); + + test('produces expected tokens', async () => { + const test = testDisposables.add( + new TestSimpleDecoder(), + ); + + await test.run( + ' hello world\nhow are\t you?\v\n\n (test) [!@#$%^&*_+=]\f \n\t\t🤗❤ \t\n hey\vthere\r\n\r\n', + [ + // first line + new Space(new Range(1, 1, 1, 2)), + new Word(new Range(1, 2, 1, 7), 'hello'), + new Space(new Range(1, 7, 1, 8)), + new Word(new Range(1, 8, 1, 13), 'world'), + new NewLine(new Range(1, 13, 1, 14)), + // second line + new Word(new Range(2, 1, 2, 4), 'how'), + new Space(new Range(2, 4, 2, 5)), + new Word(new Range(2, 5, 2, 8), 'are'), + new Tab(new Range(2, 8, 2, 9)), + new Space(new Range(2, 9, 2, 10)), + new Word(new Range(2, 10, 2, 14), 'you?'), + new VerticalTab(new Range(2, 14, 2, 15)), + new NewLine(new Range(2, 15, 2, 16)), + // third line + new NewLine(new Range(3, 1, 3, 2)), + // fourth line + new Space(new Range(4, 1, 4, 2)), + new Space(new Range(4, 2, 4, 3)), + new Space(new Range(4, 3, 4, 4)), + new Word(new Range(4, 4, 4, 10), '(test)'), + new Space(new Range(4, 10, 4, 11)), + new Space(new Range(4, 11, 4, 12)), + new Word(new Range(4, 12, 4, 25), '[!@#$%^&*_+=]'), + new FormFeed(new Range(4, 25, 4, 26)), + new Space(new Range(4, 26, 4, 27)), + new Space(new Range(4, 27, 4, 28)), + new NewLine(new Range(4, 28, 4, 29)), + // fifth line + new Tab(new Range(5, 1, 5, 2)), + new Tab(new Range(5, 2, 5, 3)), + new Word(new Range(5, 3, 5, 6), '🤗❤'), + new Space(new Range(5, 6, 5, 7)), + new Tab(new Range(5, 7, 5, 8)), + new NewLine(new Range(5, 8, 5, 9)), + // sixth line + new Space(new Range(6, 1, 6, 2)), + new Word(new Range(6, 2, 6, 5), 'hey'), + new VerticalTab(new Range(6, 5, 6, 6)), + new Word(new Range(6, 6, 6, 11), 'there'), + new CarriageReturn(new Range(6, 11, 6, 12)), + new NewLine(new Range(6, 12, 6, 13)), + // seventh line + new CarriageReturn(new Range(7, 1, 7, 2)), + new NewLine(new Range(7, 2, 7, 3)), + ], + ); + }); +}); diff --git a/src/vs/editor/test/common/utils/testDecoder.ts b/src/vs/editor/test/common/utils/testDecoder.ts new file mode 100644 index 0000000000000..e9ee9ce1067ea --- /dev/null +++ b/src/vs/editor/test/common/utils/testDecoder.ts @@ -0,0 +1,115 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { VSBuffer } from '../../../../base/common/buffer.js'; +import { randomInt } from '../../../../base/common/numbers.js'; +import { BaseToken } from '../../../common/codecs/baseToken.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { WriteableStream } from '../../../../base/common/stream.js'; +import { BaseDecoder } from '../../../../base/common/codecs/baseDecoder.js'; + +/** + * (pseudo)Random boolean generator. + * + * ## Examples + * + * ```typsecript + * randomBoolean(); // generates either `true` or `false` + * ``` + */ +const randomBoolean = (): boolean => { + return Math.random() > 0.5; +}; + +/** + * A reusable test utility that asserts that the given decoder + * produces the expected `expectedTokens` sequence of tokens. + * + * ## Examples + * + * ```typescript + * const stream = newWriteableStream(null); + * const decoder = testDisposables.add(new LinesDecoder(stream)); + * + * // create a new test utility instance + * const test = testDisposables.add(new TestDecoder(stream, decoder)); + * + * // run the test + * await test.run( + * ' hello world\n', + * [ + * new Line(1, ' hello world'), + * new NewLine(new Range(1, 13, 1, 14)), + * ], + * ); + */ +export class TestDecoder> extends Disposable { + constructor( + private readonly stream: WriteableStream, + private readonly decoder: D, + ) { + super(); + + this._register(this.decoder); + } + + /** + * Run the test sending the `inputData` data to the stream and asserting + * that the decoder produces the `expectedTokens` sequence of tokens. + */ + public async run( + inputData: string, + expectedTokens: readonly T[], + ): Promise { + // write the data to the stream after a short delay to ensure + // that the the data is sent after the reading loop below + setTimeout(() => { + let inputDataBytes = VSBuffer.fromString(inputData); + + // write the input data to the stream in multiple random-length chunks + while (inputDataBytes.byteLength > 0) { + const dataToSend = inputDataBytes.slice(0, randomInt(inputDataBytes.byteLength)); + this.stream.write(dataToSend); + inputDataBytes = inputDataBytes.slice(dataToSend.byteLength); + } + + this.stream.end(); + }, 25); + + // randomly use either the `async iterator` or the `.consume()` + // variants of getting tokens, they both must yield equal results + const receivedTokens: T[] = []; + if (randomBoolean()) { + // test the `async iterator` code path + for await (const token of this.decoder) { + if (token === null) { + break; + } + + receivedTokens.push(token); + } + } else { + // test the `.consume()` code path + receivedTokens.push(...(await this.decoder.consumeAll())); + } + + for (let i = 0; i < expectedTokens.length; i++) { + const expectedToken = expectedTokens[i]; + const receivedtoken = receivedTokens[i]; + + assert( + receivedtoken.equals(expectedToken), + `Expected token '${i}' to be '${expectedToken}', got '${receivedtoken}'.`, + ); + } + + assert.strictEqual( + receivedTokens.length, + expectedTokens.length, + 'Must produce correct number of tokens.', + ); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 998fcc7afb32e..39bb44149b6e7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -84,6 +84,7 @@ import { ChatEditingSessionState, IChatEditingService, IChatEditingSession, Work import { IChatRequestVariableEntry, isPasteVariableEntry } from '../common/chatModel.js'; import { ChatRequestDynamicVariablePart } from '../common/chatParserTypes.js'; import { IChatFollowup } from '../common/chatService.js'; +import { IChatVariablesService } from '../common/chatVariables.js'; import { IChatResponseViewModel } from '../common/chatViewModel.js'; import { IChatHistoryEntry, IChatInputState, IChatWidgetHistoryService } from '../common/chatWidgetHistoryService.js'; import { ILanguageModelChatMetadata, ILanguageModelsService } from '../common/languageModels.js'; @@ -99,6 +100,7 @@ import { ChatEditingRemoveAllFilesAction, ChatEditingShowChangesAction } from '. import { ChatEditingSaveAllAction } from './chatEditorSaving.js'; import { ChatFollowups } from './chatFollowups.js'; import { IChatViewState } from './chatWidget.js'; +import { ChatFileReference } from './contrib/chatDynamicVariables/chatFileReference.js'; import { ChatImplicitContext } from './contrib/chatImplicitContext.js'; const $ = dom.$; @@ -150,12 +152,32 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return this._attachmentModel; } - public getAttachedAndImplicitContext(): IChatRequestVariableEntry[] { + public getAttachedAndImplicitContext(sessionId: string): IChatRequestVariableEntry[] { const contextArr = [...this.attachmentModel.attachments]; if (this.implicitContext?.enabled && this.implicitContext.value) { contextArr.push(this.implicitContext.toBaseEntry()); } + // factor in nested file references into the implicit context + const variables = this.variableService.getDynamicVariables(sessionId); + for (const variable of variables) { + if (!(variable instanceof ChatFileReference)) { + continue; + } + + for (const childUri of variable.validFileReferenceUris) { + contextArr.push({ + id: variable.id, + name: basename(childUri.path), + value: childUri, + isSelection: false, + enabled: true, + isFile: true, + isDynamic: true, + }); + } + } + return contextArr; } @@ -287,6 +309,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge @ITextModelService private readonly textModelResolverService: ITextModelService, @IStorageService private readonly storageService: IStorageService, @ILabelService private readonly labelService: ILabelService, + @IChatVariablesService private readonly variableService: IChatVariablesService, ) { super(); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 800e82d952eb1..3bb7d3b2285a0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1006,7 +1006,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } } - let attachedContext = this.inputPart.getAttachedAndImplicitContext(); + let attachedContext = this.inputPart.getAttachedAndImplicitContext(this.viewModel.sessionId); let workingSet: URI[] | undefined; if (this.location === ChatAgentLocation.EditingSession) { const currentEditingSession = this.chatEditingService.currentEditingSessionObs.get(); diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts index 9d1a1e1945034..f4a6d525ce8e8 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts @@ -15,7 +15,7 @@ import { ITextModelService } from '../../../../../editor/common/services/resolve import { localize } from '../../../../../nls.js'; import { Action2, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; -import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { ILabelService } from '../../../../../platform/label/common/label.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; import { AnythingQuickAccessProviderRunOptions, IQuickAccessOptions } from '../../../../../platform/quickinput/common/quickAccess.js'; @@ -24,14 +24,23 @@ import { IChatWidget } from '../chat.js'; import { ChatWidget, IChatWidgetContrib } from '../chatWidget.js'; import { IChatRequestVariableValue, IChatVariablesService, IDynamicVariable } from '../../common/chatVariables.js'; import { ISymbolQuickPickItem } from '../../../search/browser/symbolsQuickAccess.js'; +import { ChatFileReference } from './chatDynamicVariables/chatFileReference.js'; +import { PromptFileReference } from '../../common/promptFileReference.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; export const dynamicVariableDecorationType = 'chat-dynamic-variable'; +/** + * Type of dynamic variables. Can be either a file reference or + * another dynamic variable (e.g., a `#sym`, `#kb`, etc.). + */ +type TDynamicVariable = IDynamicVariable | ChatFileReference; + export class ChatDynamicVariableModel extends Disposable implements IChatWidgetContrib { public static readonly ID = 'chatDynamicVariableModel'; - private _variables: IDynamicVariable[] = []; - get variables(): ReadonlyArray { + private _variables: TDynamicVariable[] = []; + get variables(): ReadonlyArray { return [...this._variables]; } @@ -42,8 +51,11 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC constructor( private readonly widget: IChatWidget, @ILabelService private readonly labelService: ILabelService, + @IConfigurationService private readonly configService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); + this._register(widget.inputEditor.onDidChangeModelContent(e => { e.changes.forEach(c => { // Don't mutate entries in _variables, since they will be returned from the getter @@ -60,18 +72,23 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC }]); this.widget.refreshParsedInput(); } + + // dispose the reference if possible before dropping it off + if ('dispose' in ref && typeof ref.dispose === 'function') { + ref.dispose(); + } + return null; } else if (Range.compareRangesUsingStarts(ref.range, c.range) > 0) { const delta = c.text.length - c.rangeLength; - return { - ...ref, - range: { - startLineNumber: ref.range.startLineNumber, - startColumn: ref.range.startColumn + delta, - endLineNumber: ref.range.endLineNumber, - endColumn: ref.range.endColumn + delta - } + ref.range = { + startLineNumber: ref.range.startLineNumber, + startColumn: ref.range.startColumn + delta, + endLineNumber: ref.range.endLineNumber, + endColumn: ref.range.endColumn + delta, }; + + return ref; } return ref; @@ -83,7 +100,15 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC } getInputState(): any { - return this.variables; + return this.variables + .map((variable: TDynamicVariable) => { + // return underlying `IDynamicVariable` object for file references + if (variable instanceof ChatFileReference) { + return variable.reference; + } + + return variable; + }); } setInputState(s: any): void { @@ -91,14 +116,39 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC s = []; } - this._variables = s.filter(isDynamicVariable); - this.updateDecorations(); + this.disposeVariables(); + this._variables = []; + + for (const variable of s) { + if (!isDynamicVariable(variable)) { + continue; + } + + this.addReference(variable); + } } addReference(ref: IDynamicVariable): void { - this._variables.push(ref); + // use `ChatFileReference` for file references and `IDynamicVariable` for other variables + const promptSnippetsEnabled = PromptFileReference.promptSnippetsEnabled(this.configService); + const variable = (ref.id === 'vscode.file' && promptSnippetsEnabled) + ? this.instantiationService.createInstance(ChatFileReference, ref) + : ref; + + this._variables.push(variable); this.updateDecorations(); this.widget.refreshParsedInput(); + + // if the `prompt snippets` feature is enabled, and file is a `prompt snippet`, + // start resolving nested file references immediatelly and subscribe to updates + if (variable instanceof ChatFileReference && variable.isPromptSnippetFile) { + // subscribe to variable changes + variable.onUpdate(() => { + this.updateDecorations(); + }); + // start resolving the file references + variable.resolve(); + } } private updateDecorations(): void { @@ -120,6 +170,22 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC return undefined; } } + + /** + * Dispose all existing variables. + */ + private disposeVariables(): void { + for (const variable of this._variables) { + if ('dispose' in variable && typeof variable.dispose === 'function') { + variable.dispose(); + } + } + } + + public override dispose() { + this.disposeVariables(); + super.dispose(); + } } /** diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables/chatFileReference.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables/chatFileReference.ts new file mode 100644 index 0000000000000..2a5ecdf504853 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables/chatFileReference.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 { URI } from '../../../../../../base/common/uri.js'; +import { assert } from '../../../../../../base/common/assert.js'; +import { IDynamicVariable } from '../../../common/chatVariables.js'; +import { IRange } from '../../../../../../editor/common/core/range.js'; +import { PromptFileReference } from '../../../common/promptFileReference.js'; +import { IFileService } from '../../../../../../platform/files/common/files.js'; +import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; + +/** + * A wrapper class for an `IDynamicVariable` object that that adds functionality + * to parse nested file references of this variable. + * See {@link PromptFileReference} for details. + */ +export class ChatFileReference extends PromptFileReference implements IDynamicVariable { + /** + * @throws if the `data` reference is no an instance of `URI`. + */ + constructor( + public readonly reference: IDynamicVariable, + @IFileService fileService: IFileService, + @IConfigurationService configService: IConfigurationService, + ) { + const { data } = reference; + + assert( + data instanceof URI, + `Variable data must be an URI, got '${data}'.`, + ); + + super(data, fileService, configService); + } + + /** + * Note! below are the getters that simply forward to the underlying `IDynamicVariable` object; + * while we could implement the logic generically using the `Proxy` class here, it's hard + * to make Typescript to recognize this generic implementation correctly + */ + + public get id() { + return this.reference.id; + } + + public get range() { + return this.reference.range; + } + + public set range(range: IRange) { + this.reference.range = range; + } + + public get data(): URI { + return this.uri; + } + + public get prefix() { + return this.reference.prefix; + } + + public get isFile() { + return this.reference.isFile; + } + + public get fullName() { + return this.reference.fullName; + } + + public get icon() { + return this.reference.icon; + } + + public get modelDescription() { + return this.reference.modelDescription; + } +} diff --git a/src/vs/workbench/contrib/chat/common/codecs/chatPromptCodec/chatPromptCodec.ts b/src/vs/workbench/contrib/chat/common/codecs/chatPromptCodec/chatPromptCodec.ts new file mode 100644 index 0000000000000..7522f5acd0b2d --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/codecs/chatPromptCodec/chatPromptCodec.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from '../../../../../../base/common/buffer.js'; +import { ReadableStream } from '../../../../../../base/common/stream.js'; +import { ChatPromptDecoder, TChatPromptToken } from './chatPromptDecoder.js'; +import { ICodec } from '../../../../../../base/common/codecs/types/ICodec.js'; + +/** + * `ChatPromptCodec` type is a `ICodec` with specific types for + * stream messages and return types of the `encode`/`decode` functions. + * @see {@linkcode ICodec} + */ +interface IChatPromptCodec extends ICodec { + /** + * Decode a stream of `VSBuffer`s into a stream of `TChatPromptToken`s. + * + * @see {@linkcode TChatPromptToken} + * @see {@linkcode VSBuffer} + * @see {@linkcode ChatPromptDecoder} + */ + decode: (value: ReadableStream) => ChatPromptDecoder; +} + +/** + * Codec that is capable to encode and decode tokens of an AI chatbot prompt message. + */ +export const ChatPromptCodec: IChatPromptCodec = Object.freeze({ + /** + * Encode a stream of `TChatPromptToken`s into a stream of `VSBuffer`s. + * + * @see {@linkcode ReadableStream} + * @see {@linkcode VSBuffer} + */ + encode: (_stream: ReadableStream): ReadableStream => { + throw new Error('The `encode` method is not implemented.'); + }, + + /** + * Decode a of `VSBuffer`s into a readable of `TChatPromptToken`s. + * + * @see {@linkcode TChatPromptToken} + * @see {@linkcode VSBuffer} + * @see {@linkcode ChatPromptDecoder} + * @see {@linkcode ReadableStream} + */ + decode: (stream: ReadableStream): ChatPromptDecoder => { + return new ChatPromptDecoder(stream); + }, +}); diff --git a/src/vs/workbench/contrib/chat/common/codecs/chatPromptCodec/chatPromptDecoder.ts b/src/vs/workbench/contrib/chat/common/codecs/chatPromptCodec/chatPromptDecoder.ts new file mode 100644 index 0000000000000..57b7f0955b82c --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/codecs/chatPromptCodec/chatPromptDecoder.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { FileReference } from './tokens/fileReference.js'; +import { VSBuffer } from '../../../../../../base/common/buffer.js'; +import { ReadableStream } from '../../../../../../base/common/stream.js'; +import { BaseDecoder } from '../../../../../../base/common/codecs/baseDecoder.js'; +import { Word } from '../../../../../../editor/common/codecs/simpleCodec/tokens/word.js'; +import { SimpleDecoder, TSimpleToken } from '../../../../../../editor/common/codecs/simpleCodec/simpleDecoder.js'; + +/** + * Tokens handled by the `ChatPromptDecoder` decoder. + */ +export type TChatPromptToken = FileReference; + +/** + * Decoder for the common chatbot prompt message syntax. + * For instance, the file references `#file:./path/file.md` are handled by this decoder. + */ +export class ChatPromptDecoder extends BaseDecoder { + constructor( + stream: ReadableStream, + ) { + super(new SimpleDecoder(stream)); + } + + protected override onStreamData(simpleToken: TSimpleToken): void { + // handle the word tokens only + if (!(simpleToken instanceof Word)) { + return; + } + + // handle file references only for now + const { text } = simpleToken; + if (!text.startsWith(FileReference.TOKEN_START)) { + return; + } + + this._onData.fire( + FileReference.fromWord(simpleToken), + ); + } +} diff --git a/src/vs/workbench/contrib/chat/common/codecs/chatPromptCodec/tokens/fileReference.ts b/src/vs/workbench/contrib/chat/common/codecs/chatPromptCodec/tokens/fileReference.ts new file mode 100644 index 0000000000000..5a68344a7575b --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/codecs/chatPromptCodec/tokens/fileReference.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { assert } from '../../../../../../../base/common/assert.js'; +import { Range } from '../../../../../../../editor/common/core/range.js'; +import { BaseToken } from '../../../../../../../editor/common/codecs/baseToken.js'; +import { Word } from '../../../../../../../editor/common/codecs/simpleCodec/tokens/word.js'; + +// Start sequence for a file reference token in a prompt. +const TOKEN_START: string = '#file:'; + +/** + * Object represents a file reference token inside a chatbot prompt. + */ +export class FileReference extends BaseToken { + // Start sequence for a file reference token in a prompt. + public static readonly TOKEN_START = TOKEN_START; + + constructor( + range: Range, + public readonly path: string, + ) { + super(range); + } + + /** + * Get full text of the file reference token. + */ + public get text(): string { + return `${TOKEN_START}${this.path}`; + } + + /** + * Create a file reference token out of a generic `Word`. + * @throws if the word does not conform to the expected format or if + * the reference is an invalid `URI`. + */ + public static fromWord(word: Word): FileReference { + const { text } = word; + + assert( + text.startsWith(TOKEN_START), + `The reference must start with "${TOKEN_START}", got ${text}.`, + ); + + const maybeReference = text.split(TOKEN_START); + + assert( + maybeReference.length === 2, + `The expected reference format is "${TOKEN_START}:filesystem-path", got ${text}.`, + ); + + const [first, second] = maybeReference; + + assert( + first === '', + `The reference must start with "${TOKEN_START}", got ${first}.`, + ); + + assert( + // Note! this accounts for both cases when second is `undefined` or `empty` + // and we don't care about rest of the "falsy" cases here + !!second, + `The reference path must be defined, got ${second}.`, + ); + + const reference = new FileReference( + word.range, + second, + ); + + return reference; + } + + /** + * Check if this token is equal to another one. + */ + public override equals(other: T): boolean { + if (!super.sameRange(other.range)) { + return false; + } + + if (!(other instanceof FileReference)) { + return false; + } + + return this.text === other.text; + } + + /** + * Return a string representation of the token. + */ + public override toString(): string { + return `file-ref("${this.text}")${this.range}`; + } +} diff --git a/src/vs/workbench/contrib/chat/common/promptFileReference.ts b/src/vs/workbench/contrib/chat/common/promptFileReference.ts new file mode 100644 index 0000000000000..878c800d78546 --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/promptFileReference.ts @@ -0,0 +1,414 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from '../../../../base/common/uri.js'; +import { Emitter } from '../../../../base/common/event.js'; +import { extUri } from '../../../../base/common/resources.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { Location } from '../../../../editor/common/languages.js'; +import { ChatPromptCodec } from './codecs/chatPromptCodec/chatPromptCodec.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { FileOpenFailed, NonPromptSnippetFile, RecursiveReference } from './promptFileReferenceErrors.js'; +import { FileChangesEvent, FileChangeType, IFileService, IFileStreamContent } from '../../../../platform/files/common/files.js'; + +/** + * Error conditions that may happen during the file reference resolution. + */ +export type TErrorCondition = FileOpenFailed | RecursiveReference | NonPromptSnippetFile; + +/** + * File extension for the prompt snippet files. + */ +const PROMP_SNIPPET_FILE_EXTENSION: string = '.prompt.md'; + +/** + * Configuration key for the prompt snippets feature. + */ +const PROMPT_SNIPPETS_CONFIG_KEY: string = 'chat.experimental.prompt-snippets'; + +/** + * Represents a file reference in the chatbot prompt, e.g. `#file:./path/to/file.md`. + * Contains logic to resolve all nested file references in the target file and all + * referenced child files recursively, if any. + * + * ## Examples + * + * ```typescript + * const fileReference = new PromptFileReference( + * URI.file('/path/to/file.md'), + * fileService, + * ); + * + * // subscribe to updates to the file reference tree + * fileReference.onUpdate(() => { + * // .. do something with the file reference tree .. + * // e.g. get URIs of all resolved file references in the tree + * const resolved = fileReference + * // get all file references as a flat array + * .flatten() + * // remove self from the list if only child references are needed + * .slice(1) + * // filter out unresolved references + * .filter(reference => reference.resolveFailed === flase) + * // convert to URIs only + * .map(reference => reference.uri); + * + * console.log(resolved); + * }); + * + * // *optional* if need to re-resolve file references when target files change + * // note that this does not sets up filesystem listeners for nested file references + * fileReference.addFilesystemListeners(); + * + * // start resolving the file reference tree; this can also be `await`ed if needed + * // to wait for the resolution on the main file reference to complete (the nested + * // references can still be resolving in the background) + * fileReference.resolve(); + * + * // don't forget to dispose when no longer needed! + * fileReference.dispose(); + * ``` + */ +export class PromptFileReference extends Disposable { + /** + * Child references of the current one. + */ + protected readonly children: PromptFileReference[] = []; + + /** + * The event is fired when nested prompt snippet references are updated, if any. + */ + private readonly _onUpdate = this._register(new Emitter()); + + private _errorCondition?: TErrorCondition; + /** + * If file reference resolution fails, this attribute will be set + * to an error instance that describes the error condition. + */ + public get errorCondition(): TErrorCondition | undefined { + return this._errorCondition; + } + + /** + * Whether file reference resolution was attempted at least once. + */ + private _resolveAttempted: boolean = false; + /** + * Whether file references resolution failed. + * Set to `undefined` if the `resolve` method hasn't been ever called yet. + */ + public get resolveFailed(): boolean | undefined { + if (!this._resolveAttempted) { + return undefined; + } + + return !!this._errorCondition; + } + + constructor( + private readonly _uri: URI | Location, + @IFileService private readonly fileService: IFileService, + @IConfigurationService private readonly configService: IConfigurationService, + ) { + super(); + this.onFilesChanged = this.onFilesChanged.bind(this); + + // make sure the variable is updated on file changes + // but only for the prompt snippet files + if (this.isPromptSnippetFile) { + this.addFilesystemListeners(); + } + } + + /** + * Subscribe to the `onUpdate` event. + * @param callback + */ + public onUpdate(callback: () => unknown) { + this._register(this._onUpdate.event(callback)); + } + + /** + * Check if the prompt snippets feature is enabled. + * @see {@link PROMPT_SNIPPETS_CONFIG_KEY} + */ + public static promptSnippetsEnabled( + configService: IConfigurationService, + ): boolean { + const value = configService.getValue(PROMPT_SNIPPETS_CONFIG_KEY); + + if (!value) { + return false; + } + + if (typeof value === 'string') { + return value.trim().toLowerCase() === 'true'; + } + + return !!value; + } + + /** + * Check if the current reference points to a prompt snippet file. + */ + public get isPromptSnippetFile(): boolean { + return this.uri.path.endsWith(PROMP_SNIPPET_FILE_EXTENSION); + } + + /** + * Associated URI of the reference. + */ + public get uri(): URI { + return this._uri instanceof URI + ? this._uri + : this._uri.uri; + } + + /** + * Get the directory name of the file reference. + */ + public get dirname() { + return URI.joinPath(this.uri, '..'); + } + + /** + * Check if the current reference points to a given resource. + */ + public sameUri(other: URI | Location): boolean { + const otherUri = other instanceof URI ? other : other.uri; + + return this.uri.toString() === otherUri.toString(); + } + + /** + * Add file system event listeners for the current file reference. + */ + private addFilesystemListeners(): this { + this._register( + this.fileService.onDidFilesChange(this.onFilesChanged), + ); + + return this; + } + + /** + * Event handler for the `onDidFilesChange` event. + */ + private onFilesChanged(event: FileChangesEvent) { + const fileChanged = event.contains(this.uri, FileChangeType.UPDATED); + const fileDeleted = event.contains(this.uri, FileChangeType.DELETED); + if (!fileChanged && !fileDeleted) { + return; + } + + // if file is changed or deleted, re-resolve the file reference + // in the case when the file is deleted, this should result in + // failure to open the file, so the `errorCondition` field will + // be updated to an appropriate error instance and the `children` + // field will be cleared up + this.resolve(); + } + + /** + * Get file stream, if the file exsists. + */ + private async getFileStream(): Promise { + try { + // read the file first + const result = await this.fileService.readFileStream(this.uri); + + // if file exists but not a prompt snippet file, set appropriate error + // condition and return null so we don't resolve nested references in it + if (this.uri.path.endsWith(PROMP_SNIPPET_FILE_EXTENSION) === false) { + this._errorCondition = new NonPromptSnippetFile(this.uri); + + return null; + } + + return result; + } catch (error) { + this._errorCondition = new FileOpenFailed(this.uri, error); + + return null; + } + } + + /** + * Resolve the current file reference on the disk and + * all nested file references that may exist in the file. + * + * @param waitForChildren Whether need to block until all child references are resolved. + */ + public async resolve( + waitForChildren: boolean = false, + ): Promise { + return await this.resolveReference(waitForChildren); + } + + /** + * Private implementation of the {@link resolve} method, that allows + * to pass `seenReferences` list to the recursive calls to prevent + * infinite file reference recursion. + */ + private async resolveReference( + waitForChildren: boolean = false, + seenReferences: string[] = [], + ): Promise { + // remove current error condition from the previous resolve attempt, if any + delete this._errorCondition; + + // dispose current child references, if any exist from a previous resolve + this.disposeChildren(); + + // to prevent infinite file recursion, we keep track of all references in + // the current branch of the file reference tree and check if the current + // file reference has been already seen before + if (seenReferences.includes(this.uri.path)) { + seenReferences.push(this.uri.path); + + this._errorCondition = new RecursiveReference(this.uri, seenReferences); + this._resolveAttempted = true; + this._onUpdate.fire(); + + return this; + } + + // we don't care if reading the file fails below, hence can add the path + // of the current reference to the `seenReferences` set immediately, - + // even if the file doesn't exist, we would never end up in the recursion + seenReferences.push(this.uri.path); + + // try to get stream for the contents of the file, it may + // fail to multiple reasons, e.g. file doesn't exist, etc. + const fileStream = await this.getFileStream(); + this._resolveAttempted = true; + + // failed to open the file, nothing to resolve + if (fileStream === null) { + this._onUpdate.fire(); + + return this; + } + + // get all file references in the file contents + const references = await ChatPromptCodec.decode(fileStream.value).consumeAll(); + + // recursively resolve all references and add to the `children` array + // + // Note! we don't register the children references as disposables here, because we dispose them + // explicitly in the `dispose` override method of this class. This is done to prevent + // the disposables store to be littered with already-disposed child instances due to + // the fact that the `resolve` method can be called multiple times on target file changes + const childPromises = []; + for (const reference of references) { + const childUri = extUri.resolvePath(this.dirname, reference.path); + + const child = new PromptFileReference( + childUri, + this.fileService, + this.configService, + ); + + // subscribe to child updates + child.onUpdate( + this._onUpdate.fire.bind(this._onUpdate), + ); + this.children.push(child); + + // start resolving the child in the background, including its children + // Note! we have to clone the `seenReferences` list here to ensure that + // different tree branches don't interfere with each other as we + // care about the parent references when checking for recursion + childPromises.push( + child.resolveReference(waitForChildren, [...seenReferences]), + ); + } + + // if should wait for all children to resolve, block here + if (waitForChildren) { + await Promise.all(childPromises); + } + + this._onUpdate.fire(); + + return this; + } + + /** + * Dispose current child file references. + */ + private disposeChildren(): this { + for (const child of this.children) { + child.dispose(); + } + + this.children.length = 0; + this._onUpdate.fire(); + + return this; + } + + /** + * Flatten the current file reference tree into a single array. + */ + public flatten(): readonly PromptFileReference[] { + const result = []; + + // then add self to the result + result.push(this); + + // get flattened children references first + for (const child of this.children) { + result.push(...child.flatten()); + } + + return result; + } + + /** + * Get list of all valid child references. + */ + public get validChildReferences(): readonly PromptFileReference[] { + return this.flatten() + // skip the root reference itself (this variable) + .slice(1) + // filter out unresolved references + .filter((reference) => { + return (reference.resolveFailed === false) || + (reference.errorCondition instanceof NonPromptSnippetFile); + }); + } + + /** + * Get list of all valid child references as URIs. + */ + public get validFileReferenceUris(): readonly URI[] { + return this.validChildReferences + .map(child => child.uri); + } + + /** + * Check if the current reference is equal to a given one. + */ + public equals(other: PromptFileReference): boolean { + if (!this.sameUri(other.uri)) { + return false; + } + + return true; + } + + /** + * Returns a string representation of this reference. + */ + public override toString() { + return `#file:${this.uri.path}`; + } + + public override dispose() { + this.disposeChildren(); + super.dispose(); + } +} diff --git a/src/vs/workbench/contrib/chat/common/promptFileReferenceErrors.ts b/src/vs/workbench/contrib/chat/common/promptFileReferenceErrors.ts new file mode 100644 index 0000000000000..60da24e52a7fd --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/promptFileReferenceErrors.ts @@ -0,0 +1,129 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from '../../../../base/common/uri.js'; + +/** + * Base resolve error class used when file reference resolution fails. + */ +abstract class ResolveError extends Error { + constructor( + public readonly uri: URI, + message?: string, + options?: ErrorOptions, + ) { + super(message, options); + } + + /** + * Check if provided object is of the same type as this error. + */ + public sameTypeAs(other: unknown): other is typeof this { + if (other === null || other === undefined) { + return false; + } + + return other instanceof this.constructor; + } + + /** + * Check if provided object is equal to this error. + */ + public equal(other: unknown): boolean { + return this.sameTypeAs(other); + } +} + +/** + * Error that reflects the case when attempt to open target file fails. + */ +export class FileOpenFailed extends ResolveError { + constructor( + uri: URI, + public readonly originalError: unknown, + ) { + super( + uri, + `Failed to open file '${uri.toString()}': ${originalError}.`, + ); + } +} + +/** + * Error that reflects the case when attempt resolve nested file + * references failes due to a recursive reference, e.g., + * + * ```markdown + * // a.md + * #file:b.md + * ``` + * + * ```markdown + * // b.md + * #file:a.md + * ``` + */ +export class RecursiveReference extends ResolveError { + constructor( + uri: URI, + public readonly recursivePath: string[], + ) { + const references = recursivePath.join(' -> '); + + super( + uri, + `Recursive references found: ${references}.`, + ); + } + + /** + * Returns a string representation of the recursive path. + */ + public get recursivePathString(): string { + return this.recursivePath.join(' -> '); + } + + /** + * Check if provided object is of the same type as this + * error, contains the same recursive path and URI. + */ + public override equal(other: unknown): other is this { + if (!this.sameTypeAs(other)) { + return false; + } + + if (this.uri.toString() !== other.uri.toString()) { + return false; + } + + return this.recursivePathString === other.recursivePathString; + } + + /** + * Returns a string representation of the error object. + */ + public override toString(): string { + return `"${this.message}"(${this.uri})`; + } +} + +/** + * Error that reflects the case when resource URI does not point to + * a prompt snippet file, hence was not attempted to be resolved. + */ +export class NonPromptSnippetFile extends ResolveError { + constructor( + uri: URI, + message: string = '', + ) { + + const suffix = message ? `: ${message}` : ''; + + super( + uri, + `Resource at ${uri.path} is not a prompt snippet file${suffix}`, + ); + } +} diff --git a/src/vs/workbench/contrib/chat/test/common/codecs/chatPromptCodec.test.ts b/src/vs/workbench/contrib/chat/test/common/codecs/chatPromptCodec.test.ts new file mode 100644 index 0000000000000..01fd02cd1c055 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/codecs/chatPromptCodec.test.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from '../../../../../../base/common/buffer.js'; +import { Range } from '../../../../../../editor/common/core/range.js'; +import { newWriteableStream } from '../../../../../../base/common/stream.js'; +import { TestDecoder } from '../../../../../../editor/test/common/utils/testDecoder.js'; +import { FileReference } from '../../../common/codecs/chatPromptCodec/tokens/fileReference.js'; +import { ChatPromptCodec } from '../../../common/codecs/chatPromptCodec/chatPromptCodec.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import { ChatPromptDecoder, TChatPromptToken } from '../../../common/codecs/chatPromptCodec/chatPromptDecoder.js'; + +/** + * A reusable test utility that asserts that a `ChatPromptDecoder` instance + * correctly decodes `inputData` into a stream of `TChatPromptToken` tokens. + * + * ## Examples + * + * ```typescript + * // create a new test utility instance + * const test = testDisposables.add(new TestChatPromptCodec()); + * + * // run the test + * await test.run( + * ' hello #file:./some-file.md world\n', + * [ + * new FileReference( + * new Range(1, 8, 1, 28), + * './some-file.md', + * ), + * ] + * ); + */ +export class TestChatPromptCodec extends TestDecoder { + constructor() { + const stream = newWriteableStream(null); + const decoder = ChatPromptCodec.decode(stream); + + super(stream, decoder); + } +} + +suite('ChatPromptCodec', () => { + const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); + + test('produces expected tokens', async () => { + const test = testDisposables.add(new TestChatPromptCodec()); + + await test.run( + '#file:/etc/hosts some text\t\n for #file:./README.md\t testing\n ✔ purposes\n#file:LICENSE.md ✌ \t#file:.gitignore\n\n\n\t #file:/Users/legomushroom/repos/vscode ', + [ + new FileReference( + new Range(1, 1, 1, 1 + 16), + '/etc/hosts', + ), + new FileReference( + new Range(2, 7, 2, 7 + 17), + './README.md', + ), + new FileReference( + new Range(4, 1, 4, 1 + 16), + 'LICENSE.md', + ), + new FileReference( + new Range(4, 21, 4, 21 + 16), + '.gitignore', + ), + new FileReference( + new Range(7, 5, 7, 5 + 38), + '/Users/legomushroom/repos/vscode', + ), + ], + ); + }); +}); diff --git a/src/vs/workbench/contrib/chat/test/common/codecs/chatPromptDecoder.test.ts b/src/vs/workbench/contrib/chat/test/common/codecs/chatPromptDecoder.test.ts new file mode 100644 index 0000000000000..2ebadb798f189 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/codecs/chatPromptDecoder.test.ts @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from '../../../../../../base/common/buffer.js'; +import { Range } from '../../../../../../editor/common/core/range.js'; +import { newWriteableStream } from '../../../../../../base/common/stream.js'; +import { TestDecoder } from '../../../../../../editor/test/common/utils/testDecoder.js'; +import { FileReference } from '../../../common/codecs/chatPromptCodec/tokens/fileReference.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import { ChatPromptDecoder, TChatPromptToken } from '../../../common/codecs/chatPromptCodec/chatPromptDecoder.js'; + +/** + * A reusable test utility that asserts that a `ChatPromptDecoder` instance + * correctly decodes `inputData` into a stream of `TChatPromptToken` tokens. + * + * ## Examples + * + * ```typescript + * // create a new test utility instance + * const test = testDisposables.add(new TestChatPromptDecoder()); + * + * // run the test + * await test.run( + * ' hello #file:./some-file.md world\n', + * [ + * new FileReference( + * new Range(1, 8, 1, 28), + * './some-file.md', + * ), + * ] + * ); + */ +export class TestChatPromptDecoder extends TestDecoder { + constructor( + ) { + const stream = newWriteableStream(null); + const decoder = new ChatPromptDecoder(stream); + + super(stream, decoder); + } +} + +suite('ChatPromptDecoder', () => { + const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); + + test('produces expected tokens', async () => { + const test = testDisposables.add( + new TestChatPromptDecoder(), + ); + + await test.run( + '\nhaalo!\n message 👾 message #file:./path/to/file1.md \n\n \t#file:a/b/c/filename2.md\t🖖\t#file:other-file.md\nsome text #file:/some/file/with/absolute/path.md\t', + [ + new FileReference( + new Range(3, 21, 3, 21 + 24), + './path/to/file1.md', + ), + new FileReference( + new Range(5, 3, 5, 3 + 24), + 'a/b/c/filename2.md', + ), + new FileReference( + new Range(5, 31, 5, 31 + 19), + 'other-file.md', + ), + new FileReference( + new Range(6, 11, 6, 11 + 38), + '/some/file/with/absolute/path.md', + ), + ], + ); + }); +}); diff --git a/src/vs/workbench/contrib/chat/test/common/promptFileReference.test.ts b/src/vs/workbench/contrib/chat/test/common/promptFileReference.test.ts new file mode 100644 index 0000000000000..e7256cf7099a6 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/promptFileReference.test.ts @@ -0,0 +1,474 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { URI } from '../../../../../base/common/uri.js'; +import { VSBuffer } from '../../../../../base/common/buffer.js'; +import { Schemas } from '../../../../../base/common/network.js'; +import { isWindows } from '../../../../../base/common/platform.js'; +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { IFileService } from '../../../../../platform/files/common/files.js'; +import { FileService } from '../../../../../platform/files/common/fileService.js'; +import { NullPolicyService } from '../../../../../platform/policy/common/policy.js'; +import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js'; +import { PromptFileReference, TErrorCondition } from '../../common/promptFileReference.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { ConfigurationService } from '../../../../../platform/configuration/common/configurationService.js'; +import { InMemoryFileSystemProvider } from '../../../../../platform/files/common/inMemoryFilesystemProvider.js'; +import { FileOpenFailed, RecursiveReference, NonPromptSnippetFile } from '../../common/promptFileReferenceErrors.js'; +import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; + +/** + * Represents a file system node. + */ +interface IFilesystemNode { + name: string; +} + +/** + * Represents a file node. + */ +interface IFile extends IFilesystemNode { + contents: string; +} + +/** + * Represents a folder node. + */ +interface IFolder extends IFilesystemNode { + children: (IFolder | IFile)[]; +} + +/** + * Represents a file reference with an expected + * error condition value for testing purposes. + */ +class ExpectedReference extends PromptFileReference { + constructor( + uri: URI, + public readonly error: TErrorCondition | undefined, + ) { + const nullLogService = new NullLogService(); + const nullPolicyService = new NullPolicyService(); + const nullFileService = new FileService(nullLogService); + const nullConfigService = new ConfigurationService( + URI.file('/config.json'), + nullFileService, + nullPolicyService, + nullLogService, + ); + super(uri, nullFileService, nullConfigService); + + this._register(nullFileService); + this._register(nullConfigService); + } + + /** + * Override the error condition getter to + * return the provided expected error value. + */ + public override get errorCondition() { + return this.error; + } +} + +/** + * A reusable test utility to test the `PromptFileReference` class. + */ +class TestPromptFileReference extends Disposable { + constructor( + private readonly fileStructure: IFolder, + private readonly rootFileUri: URI, + private readonly expectedReferences: ExpectedReference[], + @IFileService private readonly fileService: IFileService, + @IConfigurationService private readonly configService: IConfigurationService, + ) { + super(); + + // ensure all the expected references are disposed + for (const expectedReference of this.expectedReferences) { + this._register(expectedReference); + } + + // create in-memory file system + const fileSystemProvider = this._register(new InMemoryFileSystemProvider()); + this._register(this.fileService.registerProvider(Schemas.file, fileSystemProvider)); + } + + /** + * Run the test. + */ + public async run() { + // create the files structure on the disk + await this.createFolder( + this.fileService, + this.fileStructure, + ); + + // start resolving references for the specified root file + const rootReference = this._register(new PromptFileReference( + this.rootFileUri, + this.fileService, + this.configService, + )); + + // resolve the root file reference including all nested references + const resolvedReferences = (await rootReference.resolve(true)) + .flatten(); + + assert.strictEqual( + resolvedReferences.length, + this.expectedReferences.length, + [ + `\nExpected(${this.expectedReferences.length}): [\n ${this.expectedReferences.join('\n ')}\n]`, + `Received(${resolvedReferences.length}): [\n ${resolvedReferences.join('\n ')}\n]`, + ].join('\n') + ); + + for (let i = 0; i < this.expectedReferences.length; i++) { + const expectedReference = this.expectedReferences[i]; + const resolvedReference = resolvedReferences[i]; + + assert( + resolvedReference.equals(expectedReference), + [ + `Expected ${i}th resolved reference to be ${expectedReference}`, + `got ${resolvedReference}.`, + ].join(', '), + ); + + if (expectedReference.errorCondition === undefined) { + assert( + resolvedReference.errorCondition === undefined, + [ + `Expected ${i}th error condition to be 'undefined'`, + `got '${resolvedReference.errorCondition}'.`, + ].join(', '), + ); + continue; + } + + assert( + expectedReference.errorCondition.equal(resolvedReference.errorCondition), + [ + `Expected ${i}th error condition to be '${expectedReference.errorCondition}'`, + `got '${resolvedReference.errorCondition}'.`, + ].join(', '), + ); + } + } + + /** + * Create the provided filesystem folder structure. + */ + async createFolder( + fileService: IFileService, + folder: IFolder, + parentFolder?: URI, + ): Promise { + const folderUri = parentFolder + ? URI.joinPath(parentFolder, folder.name) + : URI.file(folder.name); + + if (await fileService.exists(folderUri)) { + await fileService.del(folderUri); + } + await fileService.createFolder(folderUri); + + for (const child of folder.children) { + const childUri = URI.joinPath(folderUri, child.name); + // create child file + if ('contents' in child) { + await fileService.writeFile(childUri, VSBuffer.fromString(child.contents)); + continue; + } + + // recursively create child filesystem structure + await this.createFolder(fileService, child, folderUri); + } + } +} + +suite('PromptFileReference (Unix)', function () { + const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); + + let instantiationService: TestInstantiationService; + setup(async () => { + const nullPolicyService = new NullPolicyService(); + const nullLogService = testDisposables.add(new NullLogService()); + const nullFileService = testDisposables.add(new FileService(nullLogService)); + const nullConfigService = testDisposables.add(new ConfigurationService( + URI.file('/config.json'), + nullFileService, + nullPolicyService, + nullLogService, + )); + instantiationService = testDisposables.add(new TestInstantiationService()); + + instantiationService.stub(IFileService, nullFileService); + instantiationService.stub(ILogService, nullLogService); + instantiationService.stub(IConfigurationService, nullConfigService); + }); + + test('resolves nested file references', async function () { + if (isWindows) { + this.skip(); + } + + const rootFolderName = 'resolves-nested-file-references'; + const rootFolder = `/${rootFolderName}`; + const rootUri = URI.file(rootFolder); + + const test = testDisposables.add(instantiationService.createInstance(TestPromptFileReference, + /** + * The file structure to be created on the disk for the test. + */ + { + name: rootFolderName, + children: [ + { + name: 'file1.prompt.md', + contents: '## Some Header\nsome contents\n ', + }, + { + name: 'file2.prompt.md', + contents: '## Files\n\t- this file #file:folder1/file3.prompt.md \n\t- also this #file:./folder1/some-other-folder/file4.prompt.md please!\n ', + }, + { + name: 'folder1', + children: [ + { + name: 'file3.prompt.md', + contents: `\n\n\t- some seemingly random #file:${rootFolder}/folder1/some-other-folder/yetAnotherFolder🤭/another-file.prompt.md contents\n some more\t content`, + }, + { + name: 'some-other-folder', + children: [ + { + name: 'file4.prompt.md', + contents: 'this file has a non-existing #file:./some-non-existing/file.prompt.md\t\treference\n\nand some non-prompt #file:./some-non-prompt-file.js', + }, + { + name: 'file.txt', + contents: 'contents of a non-prompt-snippet file', + }, + { + name: 'yetAnotherFolder🤭', + children: [ + { + name: 'another-file.prompt.md', + contents: 'another-file.prompt.md contents\t #file:../file.txt', + }, + { + name: 'one_more_file_just_in_case.prompt.md', + contents: 'one_more_file_just_in_case.prompt.md contents', + }, + ], + }, + { + name: 'some-non-prompt-file.js', + contents: 'some-non-prompt-file.js contents', + }, + ], + }, + ], + }, + ], + }, + /** + * The root file path to start the resolve process from. + */ + URI.file(`/${rootFolderName}/file2.prompt.md`), + /** + * The expected references to be resolved. + */ + [ + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './file2.prompt.md'), + undefined, + )), + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './folder1/file3.prompt.md'), + undefined, + )), + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './folder1/some-other-folder/yetAnotherFolder🤭/another-file.prompt.md'), + undefined, + )), + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './folder1/some-other-folder/file.txt'), + new NonPromptSnippetFile( + URI.joinPath(rootUri, './folder1/some-other-folder/file.txt'), + 'Ughh oh!', + ), + )), + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './folder1/some-other-folder/file4.prompt.md'), + undefined, + )), + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './folder1/some-other-folder/some-non-existing/file.prompt.md'), + new FileOpenFailed( + URI.joinPath(rootUri, './folder1/some-other-folder/some-non-existing/file.prompt.md'), + 'Some error message.', + ), + )), + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './folder1/some-other-folder/some-non-prompt-file.js'), + new NonPromptSnippetFile( + URI.joinPath(rootUri, './folder1/some-other-folder/some-non-prompt-file.js'), + 'Oh no!', + ), + )), + ] + )); + + await test.run(); + }); + + test('does not fall into infinite reference recursion', async function () { + if (isWindows) { + this.skip(); + } + + const rootFolderName = 'infinite-recursion'; + const rootFolder = `/${rootFolderName}`; + const rootUri = URI.file(rootFolder); + + const test = testDisposables.add(instantiationService.createInstance(TestPromptFileReference, + /** + * The file structure to be created on the disk for the test. + */ + { + name: rootFolderName, + children: [ + { + name: 'file1.md', + contents: '## Some Header\nsome contents\n ', + }, + { + name: 'file2.prompt.md', + contents: `## Files\n\t- this file #file:folder1/file3.prompt.md \n\t- also this #file:./folder1/some-other-folder/file4.prompt.md\n\n#file:${rootFolder}/folder1/some-other-folder/file5.prompt.md\t please!\n\t#file:./file1.md `, + }, + { + name: 'folder1', + children: [ + { + name: 'file3.prompt.md', + contents: `\n\n\t- some seemingly random #file:${rootFolder}/folder1/some-other-folder/yetAnotherFolder🤭/another-file.prompt.md contents\n some more\t content`, + }, + { + name: 'some-other-folder', + children: [ + { + name: 'file4.prompt.md', + contents: 'this file has a non-existing #file:../some-non-existing/file.prompt.md\t\treference', + }, + { + name: 'file5.prompt.md', + contents: 'this file has a relative recursive #file:../../file2.prompt.md\nreference\n ', + }, + { + name: 'yetAnotherFolder🤭', + children: [ + { + name: 'another-file.prompt.md', + // absolute path with recursion + contents: `some test goes\t\nhere #file:${rootFolder}/file2.prompt.md`, + }, + { + name: 'one_more_file_just_in_case.prompt.md', + contents: 'one_more_file_just_in_case.prompt.md contents', + }, + ], + }, + ], + }, + ], + }, + ], + }, + /** + * The root file path to start the resolve process from. + */ + URI.file(`/${rootFolderName}/file2.prompt.md`), + /** + * The expected references to be resolved. + */ + [ + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './file2.prompt.md'), + undefined, + )), + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './folder1/file3.prompt.md'), + undefined, + )), + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './folder1/some-other-folder/yetAnotherFolder🤭/another-file.prompt.md'), + undefined, + )), + /** + * This reference should be resolved as + * a recursive reference error condition. + * (the absolute reference case) + */ + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './file2.prompt.md'), + new RecursiveReference( + URI.joinPath(rootUri, './file2.prompt.md'), + [ + '/infinite-recursion/file2.prompt.md', + '/infinite-recursion/folder1/file3.prompt.md', + '/infinite-recursion/folder1/some-other-folder/yetAnotherFolder🤭/another-file.prompt.md', + '/infinite-recursion/file2.prompt.md', + ], + ), + )), + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './folder1/some-other-folder/file4.prompt.md'), + undefined, + )), + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './folder1/some-non-existing/file.prompt.md'), + new FileOpenFailed( + URI.joinPath(rootUri, './folder1/some-non-existing/file.prompt.md'), + 'Some error message.', + ), + )), + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './folder1/some-other-folder/file5.prompt.md'), + undefined, + )), + /** + * This reference should be resolved as + * a recursive reference error condition. + * (the relative reference case) + */ + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './file2.prompt.md'), + new RecursiveReference( + URI.joinPath(rootUri, './file2.prompt.md'), + [ + '/infinite-recursion/file2.prompt.md', + '/infinite-recursion/folder1/some-other-folder/file5.prompt.md', + '/infinite-recursion/file2.prompt.md', + ], + ), + )), + testDisposables.add(new ExpectedReference( + URI.joinPath(rootUri, './file1.md'), + new NonPromptSnippetFile( + URI.joinPath(rootUri, './file1.md'), + 'Uggh oh!', + ), + )), + ] + )); + + await test.run(); + }); +}); From d6bf64517af4ce9d1eeab149269012926558142c Mon Sep 17 00:00:00 2001 From: DetachHead <57028336+DetachHead@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:42:08 +1000 Subject: [PATCH 015/200] remove `javascript.inlayHints.enumMemberValues.enabled` because javascript does not have enums --- extensions/typescript-language-features/package.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index c6363dfc48e16..d93a488f08ec4 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -384,12 +384,6 @@ "markdownDescription": "%configuration.inlayHints.functionLikeReturnTypes.enabled%", "scope": "resource" }, - "javascript.inlayHints.enumMemberValues.enabled": { - "type": "boolean", - "default": false, - "markdownDescription": "%configuration.inlayHints.enumMemberValues.enabled%", - "scope": "resource" - }, "javascript.suggest.includeCompletionsForImportStatements": { "type": "boolean", "default": true, From 6be3f02482f73de545aea12e865e766a791eb4fd Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 16 Dec 2024 15:50:05 -0800 Subject: [PATCH 016/200] Inline simple methods These don't add much --- .../src/typescriptServiceClient.ts | 48 +++++++------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 7e18022f36df3..d9f47c1b85a57 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -12,7 +12,7 @@ import { Schemes } from './configuration/schemes'; import { IExperimentationTelemetryReporter } from './experimentTelemetryReporter'; import { DiagnosticKind, DiagnosticsManager } from './languageFeatures/diagnostics'; import { Logger } from './logging/logger'; -import { TelemetryProperties, TelemetryReporter, VSCodeTelemetryReporter } from './logging/telemetry'; +import { TelemetryReporter, VSCodeTelemetryReporter } from './logging/telemetry'; import Tracer from './logging/tracer'; import { ProjectType, inferredProjectCompilerOptions } from './tsconfig'; import { API } from './tsServer/api'; @@ -319,7 +319,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType public restartTsServer(fromUserAction = false): void { if (this.serverState.type === ServerState.Type.Running) { - this.info('Killing TS Server'); + this.logger.info('Killing TS Server'); this.isRestarting = true; this.serverState.server.kill(); } @@ -372,18 +372,6 @@ export default class TypeScriptServiceClient extends Disposable implements IType return this._onReady!.promise.then(f); } - private info(message: string, ...data: any[]): void { - this.logger.info(message, ...data); - } - - private error(message: string, ...data: any[]): void { - this.logger.error(message, ...data); - } - - private logTelemetry(eventName: string, properties?: TelemetryProperties) { - this.telemetryReporter.logTelemetry(eventName, properties); - } - public ensureServiceStarted() { if (this.serverState.type !== ServerState.Type.Running) { this.startService(); @@ -392,15 +380,15 @@ export default class TypeScriptServiceClient extends Disposable implements IType private token: number = 0; private startService(resendModels: boolean = false): ServerState.State { - this.info(`Starting TS Server`); + this.logger.info(`Starting TS Server`); if (this.isDisposed) { - this.info(`Not starting server: disposed`); + this.logger.info(`Not starting server: disposed`); return ServerState.None; } if (this.hasServerFatallyCrashedTooManyTimes) { - this.info(`Not starting server: too many crashes`); + this.logger.info(`Not starting server: too many crashes`); return ServerState.None; } @@ -412,10 +400,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType version = this._versionManager.currentVersion; } - this.info(`Using tsserver from: ${version.path}`); + this.logger.info(`Using tsserver from: ${version.path}`); const nodePath = this._nodeVersionManager.currentVersion; if (nodePath) { - this.info(`Using Node installation from ${nodePath} to run TS Server`); + this.logger.info(`Using Node installation from ${nodePath} to run TS Server`); } this.resetWatchers(); @@ -451,7 +439,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType "typeScriptVersionSource": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ - this.logTelemetry('tsserver.spawned', { + this.telemetryReporter.logTelemetry('tsserver.spawned', { ...typeScriptServerEnvCommonProperties, localTypeScriptVersion: this.versionProvider.localVersion ? this.versionProvider.localVersion.displayName : '', typeScriptVersionSource: version.source, @@ -468,9 +456,9 @@ export default class TypeScriptServiceClient extends Disposable implements IType } this.serverState = new ServerState.Errored(err, handle.tsServerLog); - this.error('TSServer errored with error.', err); + this.logger.error('TSServer errored with error.', err); if (handle.tsServerLog?.type === 'file') { - this.error(`TSServer log file: ${handle.tsServerLog.uri.fsPath}`); + this.logger.error(`TSServer log file: ${handle.tsServerLog.uri.fsPath}`); } /* __GDPR__ @@ -482,7 +470,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType ] } */ - this.logTelemetry('tsserver.error', { + this.telemetryReporter.logTelemetry('tsserver.error', { ...typeScriptServerEnvCommonProperties }); this.serviceExited(false, apiVersion); @@ -490,7 +478,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType handle.onExit((data: TypeScriptServerExitEvent) => { const { code, signal } = data; - this.error(`TSServer exited. Code: ${code}. Signal: ${signal}`); + this.logger.error(`TSServer exited. Code: ${code}. Signal: ${signal}`); // In practice, the exit code is an integer with no ties to any identity, // so it can be classified as SystemMetaData, rather than CallstackOrException. @@ -505,7 +493,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType "signal" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } } */ - this.logTelemetry('tsserver.exitWithCode', { + this.telemetryReporter.logTelemetry('tsserver.exitWithCode', { ...typeScriptServerEnvCommonProperties, code: code ?? undefined, signal: signal ?? undefined, @@ -517,7 +505,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType } if (handle.tsServerLog?.type === 'file') { - this.info(`TSServer log file: ${handle.tsServerLog.uri.fsPath}`); + this.logger.info(`TSServer log file: ${handle.tsServerLog.uri.fsPath}`); } this.serviceExited(!this.isRestarting, apiVersion); this.isRestarting = false; @@ -678,7 +666,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType ] } */ - this.logTelemetry('serviceExited'); + this.telemetryReporter.logTelemetry('serviceExited'); } else if (diff < 60 * 1000 * 5 /* 5 Minutes */) { this.lastStart = Date.now(); if (!this._isPromptingAfterCrash) { @@ -956,14 +944,14 @@ export default class TypeScriptServiceClient extends Disposable implements IType "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ - this.logTelemetry('fatalError', { ...(error instanceof TypeScriptServerError ? error.telemetry : { command }) }); + this.telemetryReporter.logTelemetry('fatalError', { ...(error instanceof TypeScriptServerError ? error.telemetry : { command }) }); console.error(`A non-recoverable error occurred while executing tsserver command: ${command}`); if (error instanceof TypeScriptServerError && error.serverErrorText) { console.error(error.serverErrorText); } if (this.serverState.type === ServerState.Type.Running) { - this.info('Killing TS Server'); + this.logger.info('Killing TS Server'); const logfile = this.serverState.server.tsServerLog; this.serverState.server.kill(); if (error instanceof TypeScriptServerError) { @@ -1235,7 +1223,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType } */ // __GDPR__COMMENT__: Other events are defined by TypeScript. - this.logTelemetry(telemetryData.telemetryEventName, properties); + this.telemetryReporter.logTelemetry(telemetryData.telemetryEventName, properties); } private configurePlugin(pluginName: string, configuration: {}): any { From 19c7c0190d58569f06e2d97f11573ea31360ba07 Mon Sep 17 00:00:00 2001 From: tcostew <143664112+tcostew@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:01:11 -0500 Subject: [PATCH 017/200] Allow Github Copilot chat to appear in QuickAccess (#210805) --- .../workbench/contrib/search/browser/anythingQuickAccess.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index 9d50072998dd1..c08b3518cf7c8 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -485,8 +485,9 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider Date: Mon, 16 Dec 2024 17:06:07 -0800 Subject: [PATCH 018/200] Use safer escaping for css url strings Fixes #236122 For css `\0000xx` escaping, if I'm understanding the spec correctly it turns out they eat the space character following them: https://www.w3.org/TR/CSS2/syndata.html#characters My previous fix didn't account for this. Instead I think a safer fix is to switch to use `Css.escape`. This often over-escapes the string by converting characters that don't need it but should be safe and it fixes the original issue --- src/vs/base/browser/cssValue.ts | 2 +- .../browser/services/decorationRenderOptions.test.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/cssValue.ts b/src/vs/base/browser/cssValue.ts index 6ee7fff792af1..84cee8522ae9d 100644 --- a/src/vs/base/browser/cssValue.ts +++ b/src/vs/base/browser/cssValue.ts @@ -62,7 +62,7 @@ export function asCSSUrl(uri: URI | null | undefined): CssFragment { if (!uri) { return asFragment(`url('')`); } - return inline`url(${stringValue(FileAccess.uriToBrowserUri(uri).toString(true))})`; + return inline`url('${asFragment(CSS.escape(FileAccess.uriToBrowserUri(uri).toString(true)))}')`; } export function className(value: string, escapingExpected = false): CssFragment { diff --git a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts index 74555b9a823a9..9608d1d779a46 100644 --- a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts +++ b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts @@ -44,7 +44,7 @@ suite('Decoration Render Options', () => { const styleSheet = s.globalStyleSheet; store.add(s.registerDecorationType('test', 'example', options)); const sheet = readStyleSheet(styleSheet); - assert(sheet.indexOf(`{background:url('https://github.com/microsoft/vscode/blob/main/resources/linux/code.png') center center no-repeat;background-size:contain;}`) >= 0); + assert(sheet.indexOf(`{background:url('${CSS.escape('https://github.com/microsoft/vscode/blob/main/resources/linux/code.png')}') center center no-repeat;background-size:contain;}`) >= 0); assert(sheet.indexOf(`{background-color:red;border-color:yellow;box-sizing: border-box;}`) >= 0); }); @@ -111,7 +111,7 @@ suite('Decoration Render Options', () => { // URI, only minimal encoding s.registerDecorationType('test', 'example', { gutterIconPath: URI.parse('data:image/svg+xml;base64,PHN2ZyB4b+') }); - assert(readStyleSheet(styleSheet).indexOf(`{background:url('data:image/svg+xml;base64,PHN2ZyB4b+') center center no-repeat;}`) > 0); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('${CSS.escape('data:image/svg+xml;base64,PHN2ZyB4b+')}') center center no-repeat;}`) > 0); s.removeDecorationType('example'); function assertBackground(url1: string, url2: string) { @@ -130,22 +130,22 @@ suite('Decoration Render Options', () => { // single quote must always be escaped/encoded s.registerDecorationType('test', 'example', { gutterIconPath: URI.file('c:\\files\\foo\\b\'ar.png') }); - assertBackground('file:///c:/files/foo/b\\000027ar.png', 'vscode-file://vscode-app/c:/files/foo/b\\000027ar.png'); + assertBackground(CSS.escape('file:///c:/files/foo/b\'ar.png'), CSS.escape('vscode-file://vscode-app/c:/files/foo/b\'ar.png')); s.removeDecorationType('example'); } else { // unix file path (used as string) s.registerDecorationType('test', 'example', { gutterIconPath: URI.file('/Users/foo/bar.png') }); - assertBackground('file:///Users/foo/bar.png', 'vscode-file://vscode-app/Users/foo/bar.png'); + assertBackground(CSS.escape('file:///Users/foo/bar.png'), CSS.escape('vscode-file://vscode-app/Users/foo/bar.png')); s.removeDecorationType('example'); // single quote must always be escaped/encoded s.registerDecorationType('test', 'example', { gutterIconPath: URI.file('/Users/foo/b\'ar.png') }); - assertBackground('file:///Users/foo/b\\000027ar.png', 'vscode-file://vscode-app/Users/foo/b\\000027ar.png'); + assertBackground(CSS.escape('file:///Users/foo/b\'ar.png'), CSS.escape('vscode-file://vscode-app/Users/foo/b\'ar.png')); s.removeDecorationType('example'); } s.registerDecorationType('test', 'example', { gutterIconPath: URI.parse('http://test/pa\'th') }); - assert(readStyleSheet(styleSheet).indexOf(`{background:url('http://test/pa\\000027th') center center no-repeat;}`) > 0); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('${CSS.escape('http://test/pa\'th')}') center center no-repeat;}`) > 0); s.removeDecorationType('example'); }); }); From 95386dec787946547812c7ee9d38cba19d6a6398 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 16 Dec 2024 19:27:10 -0600 Subject: [PATCH 019/200] make screen reader notification more descriptive, useful (#236295) --- .../accessibility/browser/accessibilityStatus.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityStatus.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityStatus.ts index 50ae856a62f0b..aa09758f5b4e9 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityStatus.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityStatus.ts @@ -13,6 +13,7 @@ import { ConfigurationTarget, IConfigurationService } from '../../../../platform import { INotificationHandle, INotificationService, NotificationPriority } from '../../../../platform/notification/common/notification.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from '../../../services/statusbar/browser/statusbar.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; export class AccessibilityStatus extends Disposable implements IWorkbenchContribution { @@ -26,7 +27,8 @@ export class AccessibilityStatus extends Disposable implements IWorkbenchContrib @IConfigurationService private readonly configurationService: IConfigurationService, @INotificationService private readonly notificationService: INotificationService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - @IStatusbarService private readonly statusbarService: IStatusbarService + @IStatusbarService private readonly statusbarService: IStatusbarService, + @IOpenerService private readonly openerService: IOpenerService, ) { super(); @@ -50,7 +52,7 @@ export class AccessibilityStatus extends Disposable implements IWorkbenchContrib private showScreenReaderNotification(): void { this.screenReaderNotification = this.notificationService.prompt( Severity.Info, - localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code?"), + localize('screenReaderDetectedExplanation.question', "Screen reader usage detected. Do you want to enable {0} to optimize the editor for screen reader usage?", 'editor.accessibilitySupport'), [{ label: localize('screenReaderDetectedExplanation.answerYes', "Yes"), run: () => { @@ -61,6 +63,12 @@ export class AccessibilityStatus extends Disposable implements IWorkbenchContrib run: () => { this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER); } + }, + { + label: localize('screenReaderDetectedExplanation.answerLearnMore', "Learn More"), + run: () => { + this.openerService.open('https://code.visualstudio.com/docs/editor/accessibility#_screen-readers'); + } }], { sticky: true, From ca3ff9f56444da1bfa4d942e7fea9154deeffbf9 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:26:30 +0100 Subject: [PATCH 020/200] SCM - quick diff should better handle untracked files (#236315) --- extensions/git/src/repository.ts | 8 +++++++- src/vs/workbench/contrib/scm/browser/quickDiffModel.ts | 4 ---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 73a54cef4afaa..21dd3b5819fab 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1035,7 +1035,13 @@ export class Repository implements Disposable { } // Ignore path that is inside a merge group - if (this.mergeGroup.resourceStates.some(r => r.resourceUri.path === uri.path)) { + if (this.mergeGroup.resourceStates.some(r => pathEquals(r.resourceUri.fsPath, uri.fsPath))) { + return undefined; + } + + // Ignore path that is untracked + if (this.untrackedGroup.resourceStates.some(r => pathEquals(r.resourceUri.path, uri.path)) || + this.workingTreeGroup.resourceStates.some(r => pathEquals(r.resourceUri.path, uri.path) && r.type === Status.UNTRACKED)) { return undefined; } diff --git a/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts b/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts index 3d1ec39c3f61b..bf9494da91cf0 100644 --- a/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts +++ b/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts @@ -214,10 +214,6 @@ export class QuickDiffModel extends Disposable { return; // disposed } - if (editorModels.every(editorModel => editorModel.textEditorModel.getValueLength() === 0)) { - result.changes = []; - } - this.setChanges(result.changes, result.mapChanges); }) .catch(err => onUnexpectedError(err)); From a5a6d6d21e8b03bf81105101125571db01ff8f23 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Dec 2024 10:02:16 +0100 Subject: [PATCH 021/200] Improve wording and flow of restart EH blocker (fix #233912) (#236311) --- .../api/browser/mainThreadCustomEditors.ts | 2 +- .../common/customTextEditorModel.ts | 2 +- .../contrib/debug/browser/debugService.ts | 2 +- ...mentWorkspaceTrustTransitionParticipant.ts | 2 +- .../browser/extensionsWorkbenchService.ts | 2 +- .../notebook/common/notebookEditorInput.ts | 4 +-- .../browser/relauncher.contribution.ts | 2 +- .../common/abstractExtensionService.ts | 29 +++++++++++++++---- .../nativeExtensionService.ts | 2 +- .../browser/userDataProfileManagement.ts | 2 +- .../workspaceEditingService.ts | 2 +- 11 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts index 4fb33a99f168b..cd15f673c2909 100644 --- a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts @@ -389,7 +389,7 @@ class MainThreadCustomEditorModel extends ResourceWorkingCopy implements ICustom this._register(workingCopyService.registerWorkingCopy(this)); this._register(extensionService.onWillStop(e => { - e.veto(true, localize('vetoExtHostRestart', "A custom editor for '{0}' is open.", this.name)); + e.veto(true, localize('vetoExtHostRestart', "An extension provided editor for '{0}' is still open that would close otherwise.", this.name)); })); } diff --git a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts index 0ca157683f9db..38648ff3dfdce 100644 --- a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts +++ b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts @@ -66,7 +66,7 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo })); this._register(extensionService.onWillStop(e => { - e.veto(true, localize('vetoExtHostRestart', "A custom text editor for '{0}' is open.", this.resource.path)); + e.veto(true, localize('vetoExtHostRestart', "An extension provided text editor for '{0}' is still open that would close otherwise.", this.name)); })); } diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 7019624359ca2..1527a4c21015b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -207,7 +207,7 @@ export class DebugService implements IDebugService { this.disposables.add(extensionService.onWillStop(evt => { evt.veto( this.model.getSessions().length > 0, - nls.localize('active debug session', 'A debug session is still running.'), + nls.localize('active debug session', 'A debug session is still running that would terminate.'), ); })); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEnablementWorkspaceTrustTransitionParticipant.ts b/src/vs/workbench/contrib/extensions/browser/extensionEnablementWorkspaceTrustTransitionParticipant.ts index 55de158e6b8f0..47065f253b469 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEnablementWorkspaceTrustTransitionParticipant.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEnablementWorkspaceTrustTransitionParticipant.ts @@ -39,7 +39,7 @@ export class ExtensionEnablementWorkspaceTrustTransitionParticipant extends Disp if (environmentService.remoteAuthority) { hostService.reload(); } else { - const stopped = await extensionService.stopExtensionHosts(localize('restartExtensionHost.reason', "Restarting extension host due to workspace trust change.")); + const stopped = await extensionService.stopExtensionHosts(localize('restartExtensionHost.reason', "Changing workspace trust")); await extensionEnablementService.updateExtensionsEnablementsWhenWorkspaceTrustChanges(); if (stopped) { extensionService.startExtensionHosts(); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 1a28f0027eec6..ae7df7d09b38c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1585,7 +1585,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } if (toAdd.length || toRemove.length) { - if (await this.extensionService.stopExtensionHosts(nls.localize('restart', "Enable or Disable extensions"), auto)) { + if (await this.extensionService.stopExtensionHosts(nls.localize('restart', "Changing extension enablement"), auto)) { await this.extensionService.startExtensionHosts({ toAdd, toRemove }); if (auto) { this.notificationService.notify({ diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts index 92c066a28e7b6..f6c4b05d618bc 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts @@ -91,8 +91,8 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { } const reason = e.auto - ? localize('vetoAutoExtHostRestart', "One of the opened editors is a notebook editor.") - : localize('vetoExtHostRestart', "Notebook '{0}' could not be saved.", this.resource.path); + ? localize('vetoAutoExtHostRestart', "An extension provided notebook for '{0}' is still open that would close otherwise.", this.getName()) + : localize('vetoExtHostRestart', "An extension provided notebook for '{0}' could not be saved.", this.getName()); e.veto((async () => { const editors = editorService.findEditors(this); diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index 3cb188be749bf..54b2cff5b80ca 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -224,7 +224,7 @@ export class WorkspaceChangeExtHostRelauncher extends Disposable implements IWor if (environmentService.remoteAuthority) { hostService.reload(); // TODO@aeschli, workaround } else if (isNative) { - const stopped = await extensionService.stopExtensionHosts(localize('restartExtensionHost.reason', "Restarting extension host due to a workspace folder change.")); + const stopped = await extensionService.stopExtensionHosts(localize('restartExtensionHost.reason', "Changing workspace folders")); if (stopped) { extensionService.startExtensionHosts(); } diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 68b71d7ce1bc4..8e63847fb9b65 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -761,12 +761,29 @@ export abstract class AbstractExtensionService extends Disposable implements IEx const vetoReasonsArray = Array.from(vetoReasons); this._logService.warn(`Extension host was not stopped because of veto (stop reason: ${reason}, veto reason: ${vetoReasonsArray.join(', ')})`); - await this._dialogService.warn( - nls.localize('extensionStopVetoMessage', "The following operation was blocked: {0}", reason), - vetoReasonsArray.length === 1 ? - nls.localize('extensionStopVetoDetailsOne', "The reason for blocking the operation: {0}", vetoReasonsArray[0]) : - nls.localize('extensionStopVetoDetailsMany', "The reasons for blocking the operation:\n- {0}", vetoReasonsArray.join('\n -')), - ); + + let overrideVeto = false; + await this._dialogService.prompt({ + type: Severity.Warning, + message: nls.localize('extensionStopVetoMessage', "Restart of extensions was prevented but is required for: {0}. Do you want to proceed anyways?", reason), + detail: vetoReasonsArray.length === 1 ? + nls.localize('extensionStopVetoDetailsOne', "Reason: {0}", vetoReasonsArray[0]) : + nls.localize('extensionStopVetoDetailsMany', "Reasons:\n- {0}", vetoReasonsArray.join('\n -')), + buttons: [ + { + label: nls.localize('ok', "OK"), + run: () => { /* noop */ } + }, + { + label: nls.localize('proceedAnyways', "Proceed Anyways"), + run: () => overrideVeto = true + } + ] + }); + + if (overrideVeto) { + return true; + } } } diff --git a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts index 8adc7c19fe7a9..33eabcc36ddf6 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts @@ -742,7 +742,7 @@ class RestartExtensionHostAction extends Action2 { async run(accessor: ServicesAccessor): Promise { const extensionService = accessor.get(IExtensionService); - const stopped = await extensionService.stopExtensionHosts(nls.localize('restartExtensionHost.reason', "Restarting extension host on explicit request.")); + const stopped = await extensionService.stopExtensionHosts(nls.localize('restartExtensionHost.reason', "An explicit request")); if (stopped) { extensionService.startExtensionHosts(); } diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts index 88b466660775c..e4b29d57e5395 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts @@ -198,7 +198,7 @@ export class UserDataProfileManagementService extends Disposable implements IUse if (shouldRestartExtensionHosts) { if (!isRemoteWindow) { - if (!(await this.extensionService.stopExtensionHosts(localize('switch profile', "Switching to a profile.")))) { + if (!(await this.extensionService.stopExtensionHosts(localize('switch profile', "Switching to a profile")))) { // If extension host did not stop, do not switch profile if (this.userDataProfilesService.profiles.some(p => p.id === this.userDataProfileService.currentProfile.id)) { await this.userDataProfilesService.setProfileForWorkspace(toWorkspaceIdentifier(this.workspaceContextService.getWorkspace()), this.userDataProfileService.currentProfile); diff --git a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts index 625f69f60b94c..c285804f40a6d 100644 --- a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts @@ -174,7 +174,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi } async enterWorkspace(workspaceUri: URI): Promise { - const stopped = await this.extensionService.stopExtensionHosts(localize('restartExtensionHost.reason', "Opening a multi-root workspace.")); + const stopped = await this.extensionService.stopExtensionHosts(localize('restartExtensionHost.reason', "Opening a multi-root workspace")); if (!stopped) { return; } From 9963eb06d9ead53d8f42d73959b47f38554d21bd Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 17 Dec 2024 10:08:35 +0100 Subject: [PATCH 022/200] Removing showing of hover on modifier key press (#236317) removing showing of hover on modifier key press --- .../hover/browser/contentHoverController.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHoverController.ts b/src/vs/editor/contrib/hover/browser/contentHoverController.ts index e44c40a947ddf..a3f67bb85f63b 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverController.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverController.ts @@ -5,7 +5,6 @@ import { DECREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID, SHOW_OR_FOCUS_HOVER_ACTION_ID } from './hoverActionIds.js'; import { IKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; -import { KeyCode } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; import { ICodeEditor, IEditorMouseEvent, IPartialEditorMouseEvent } from '../../../browser/editorBrowser.js'; import { ConfigurationChangedEvent, EditorOption } from '../../../common/config/editorOptions.js'; @@ -243,13 +242,6 @@ export class ContentHoverController extends Disposable implements IEditorContrib if (isPotentialKeyboardShortcut) { return; } - const isModifierKeyPressed = this._isModifierKeyPressed(e); - if (isModifierKeyPressed && this._mouseMoveEvent) { - const contentWidget: ContentHoverWidgetWrapper = this._getOrCreateContentWidget(); - if (contentWidget.showsOrWillShow(this._mouseMoveEvent)) { - return; - } - } this.hideContentHover(); } @@ -267,13 +259,6 @@ export class ContentHoverController extends Disposable implements IEditorContrib return moreChordsAreNeeded || isHoverAction; } - private _isModifierKeyPressed(e: IKeyboardEvent): boolean { - return e.keyCode === KeyCode.Ctrl - || e.keyCode === KeyCode.Alt - || e.keyCode === KeyCode.Meta - || e.keyCode === KeyCode.Shift; - } - public hideContentHover(): void { if (_sticky) { return; From f5e5ee4b29158b0294783dba7dd3f3b92d6e9180 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:14:27 +0100 Subject: [PATCH 023/200] SCM - remove more debt from the quick diff (#236318) --- extensions/git/src/repository.ts | 17 +++++++++++++++++ .../contrib/scm/browser/quickDiffModel.ts | 10 +--------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 21dd3b5819fab..ac2649242f2fd 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1031,17 +1031,34 @@ export class Repository implements Disposable { // Ignore path that is not inside the current repository if (this.repositoryResolver.getRepository(uri) !== this) { + this.logger.trace(`[Repository][provideOriginalResource] Resource is not part of the repository: ${uri.toString()}`); return undefined; } // Ignore path that is inside a merge group if (this.mergeGroup.resourceStates.some(r => pathEquals(r.resourceUri.fsPath, uri.fsPath))) { + this.logger.trace(`[Repository][provideOriginalResource] Resource is part of a merge group: ${uri.toString()}`); return undefined; } // Ignore path that is untracked if (this.untrackedGroup.resourceStates.some(r => pathEquals(r.resourceUri.path, uri.path)) || this.workingTreeGroup.resourceStates.some(r => pathEquals(r.resourceUri.path, uri.path) && r.type === Status.UNTRACKED)) { + this.logger.trace(`[Repository][provideOriginalResource] Resource is untracked: ${uri.toString()}`); + return undefined; + } + + const activeTabInput = window.tabGroups.activeTabGroup.activeTab?.input; + + // Ignore file that is on the right-hand side of a diff editor + if (activeTabInput instanceof TabInputTextDiff && pathEquals(activeTabInput.modified.fsPath, uri.fsPath)) { + this.logger.trace(`[Repository][provideOriginalResource] Resource is on the right-hand side of a diff editor: ${uri.toString()}`); + return undefined; + } + + // Ignore file that is on the right -hand side of a multi-file diff editor + if (activeTabInput instanceof TabInputTextMultiDiff && activeTabInput.textDiffs.some(diff => pathEquals(diff.modified.fsPath, uri.fsPath))) { + this.logger.trace(`[Repository][provideOriginalResource] Resource is on the right-hand side of a multi-file diff editor: ${uri.toString()}`); return undefined; } diff --git a/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts b/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts index bf9494da91cf0..c8a3d8b07dd9e 100644 --- a/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts +++ b/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts @@ -351,15 +351,7 @@ export class QuickDiffModel extends Disposable { } const isSynchronized = this._model.textEditorModel ? shouldSynchronizeModel(this._model.textEditorModel) : undefined; - const quickDiffs = await this.quickDiffService.getQuickDiffs(uri, this._model.getLanguageId(), isSynchronized); - - // TODO@lszomoru - find a long term solution for this - // When the QuickDiffModel is created for a diff editor, there is no - // need to compute the diff information for the `isSCM` quick diff - // provider as that information will be provided by the diff editor - return this.options.maxComputationTimeMs === undefined - ? quickDiffs.filter(quickDiff => !quickDiff.isSCM) - : quickDiffs; + return this.quickDiffService.getQuickDiffs(uri, this._model.getLanguageId(), isSynchronized); } findNextClosestChange(lineNumber: number, inclusive = true, provider?: string): number { From f283262be0d06d0c6759e52c1c47fbde06e2b172 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 17 Dec 2024 10:17:50 +0100 Subject: [PATCH 024/200] [json] `Unable to load schema, EISDIR: illegal operation on a directory (#236319) --- .../server/src/node/jsonServerMain.ts | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/extensions/json-language-features/server/src/node/jsonServerMain.ts b/extensions/json-language-features/server/src/node/jsonServerMain.ts index ad1ae439e599f..c22cd14834d3a 100644 --- a/extensions/json-language-features/server/src/node/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/node/jsonServerMain.ts @@ -8,8 +8,8 @@ import { formatError } from '../utils/runner'; import { RequestService, RuntimeEnvironment, startServer } from '../jsonServer'; import { xhr, XHRResponse, configure as configureHttpRequests, getErrorStatusDescription } from 'request-light'; -import { URI as Uri } from 'vscode-uri'; -import * as fs from 'fs'; +import { promises as fs } from 'fs'; +import * as l10n from '@vscode/l10n'; // Create a connection for the server. const connection: Connection = createConnection(); @@ -36,16 +36,17 @@ function getHTTPRequestService(): RequestService { function getFileRequestService(): RequestService { return { - getContent(location: string, encoding?: BufferEncoding) { - return new Promise((c, e) => { - const uri = Uri.parse(location); - fs.readFile(uri.fsPath, encoding, (err, buf) => { - if (err) { - return e(err); - } - c(buf.toString()); - }); - }); + async getContent(location: string, encoding?: BufferEncoding) { + try { + return (await fs.readFile(location, encoding)).toString(); + } catch (e) { + if (e.code === 'ENOENT') { + throw new Error(l10n.t('Schema not found: {0}', location)); + } else if (e.code === 'EISDIR') { + throw new Error(l10n.t('{0} is a directory, not a file', location)); + } + throw e; + } } }; } From 848e5a3495a9af7dc8523a25e12b81b267b2a4de Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 17 Dec 2024 11:09:44 +0100 Subject: [PATCH 025/200] Do not inherit outer editor's rulers (#236324) --- .../inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts index 481af0ed288d7..b2cd7d9ef5689 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts @@ -247,6 +247,7 @@ export class InlineEditsSideBySideDiff extends Disposable { bracketPairsHorizontal: false, highlightActiveIndentation: false, }, + rulers: [], padding: { top: 0, bottom: 0 }, folding: false, selectOnLineNumbers: false, From b426e026e1640a08b827e3b28274c0bea654ccf8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Dec 2024 11:26:12 +0100 Subject: [PATCH 026/200] Show remote name for files in recently opened (fix #143096) (#236309) --- src/vs/platform/label/common/label.ts | 3 ++- src/vs/workbench/browser/actions/windowActions.ts | 2 +- .../workbench/browser/parts/titlebar/menubarControl.ts | 2 +- .../contrib/search/browser/anythingQuickAccess.ts | 2 -- .../dialogs/browser/abstractFileDialogService.ts | 2 +- .../services/host/browser/browserHostService.ts | 2 +- .../host/electron-sandbox/nativeHostService.ts | 2 +- src/vs/workbench/services/label/common/labelService.ts | 10 +++++++--- 8 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/vs/platform/label/common/label.ts b/src/vs/platform/label/common/label.ts index e920d9a1c21fa..c8d520ae357b4 100644 --- a/src/vs/platform/label/common/label.ts +++ b/src/vs/platform/label/common/label.ts @@ -20,8 +20,9 @@ export interface ILabelService { * If `relative` is passed returns a label relative to the workspace root that the uri belongs to. * If `noPrefix` is passed does not tildify the label and also does not prepand the root name for relative labels in a multi root scenario. * If `separator` is passed, will use that over the defined path separator of the formatter. + * If `appendWorkspaceSuffix` is passed, will append the name of the workspace to the label. */ - getUriLabel(resource: URI, options?: { relative?: boolean; noPrefix?: boolean; separator?: '/' | '\\' }): string; + getUriLabel(resource: URI, options?: { relative?: boolean; noPrefix?: boolean; separator?: '/' | '\\'; appendWorkspaceSuffix?: boolean }): string; getUriBasenameLabel(resource: URI): string; getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | IWorkspace), options?: { verbose: Verbosity }): string; getHostLabel(scheme: string, authority?: string): string; diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index e2248c750fbb7..2fb9c8edbb3d5 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -211,7 +211,7 @@ abstract class BaseOpenRecentAction extends Action2 { resource = recent.fileUri; iconClasses = getIconClasses(modelService, languageService, resource, FileKind.FILE); openable = { fileUri: resource }; - fullLabel = recent.label || labelService.getUriLabel(resource); + fullLabel = recent.label || labelService.getUriLabel(resource, { appendWorkspaceSuffix: true }); } const { name, parentPath } = splitRecentLabel(fullLabel); diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index ac4940f1c28ce..50e6831865081 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -327,7 +327,7 @@ export abstract class MenubarControl extends Disposable { openable = { workspaceUri: uri }; } else { uri = recent.fileUri; - label = recent.label || this.labelService.getUriLabel(uri); + label = recent.label || this.labelService.getUriLabel(uri, { appendWorkspaceSuffix: true }); commandId = 'openRecentFile'; openable = { fileUri: uri }; } diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index c08b3518cf7c8..bcb65cdddfd8b 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -484,8 +484,6 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider = []; for (const editor of this.historyService.getHistory()) { const resource = editor.resource; - // allow untitled and terminal editors to go through - // allow github copilot chat to go through if (!resource) { continue; } diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index c7e890fce1dff..7223babe5ef59 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -230,7 +230,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService { } protected addFileToRecentlyOpened(uri: URI): void { - this.workspacesService.addRecentlyOpened([{ fileUri: uri, label: this.labelService.getUriLabel(uri) }]); + this.workspacesService.addRecentlyOpened([{ fileUri: uri, label: this.labelService.getUriLabel(uri, { appendWorkspaceSuffix: true }) }]); } protected async pickFolderAndOpenSimplified(schema: string, options: IPickAndOpenOptions): Promise { diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index c2a2374c6c962..afd2a3d126737 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -428,7 +428,7 @@ export class BrowserHostService extends Disposable implements IHostService { return this.labelService.getWorkspaceLabel(getWorkspaceIdentifier(openable.workspaceUri), { verbose: Verbosity.LONG }); } - return this.labelService.getUriLabel(openable.fileUri); + return this.labelService.getUriLabel(openable.fileUri, { appendWorkspaceSuffix: true }); } private shouldReuse(options: IOpenWindowOptions = Object.create(null), isFile: boolean): boolean { diff --git a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts index 8bdfe9743feaa..be5cdaa8bb6f5 100644 --- a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts +++ b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts @@ -130,7 +130,7 @@ class WorkbenchHostService extends Disposable implements IHostService { return this.labelService.getWorkspaceLabel({ id: '', configPath: openable.workspaceUri }, { verbose: Verbosity.LONG }); } - return this.labelService.getUriLabel(openable.fileUri); + return this.labelService.getUriLabel(openable.fileUri, { appendWorkspaceSuffix: true }); } private doOpenEmptyWindow(options?: IOpenEmptyWindowOptions): Promise { diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 941b49db19aa6..d99ee6ad9b462 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -208,19 +208,23 @@ export class LabelService extends Disposable implements ILabelService { return bestResult ? bestResult.formatting : undefined; } - getUriLabel(resource: URI, options: { relative?: boolean; noPrefix?: boolean; separator?: '/' | '\\' } = {}): string { + getUriLabel(resource: URI, options: { relative?: boolean; noPrefix?: boolean; separator?: '/' | '\\'; appendWorkspaceSuffix?: boolean } = {}): string { let formatting = this.findFormatting(resource); if (formatting && options.separator) { // mixin separator if defined from the outside formatting = { ...formatting, separator: options.separator }; } - const label = this.doGetUriLabel(resource, formatting, options); + let label = this.doGetUriLabel(resource, formatting, options); // Without formatting we still need to support the separator // as provided in options (https://github.com/microsoft/vscode/issues/130019) if (!formatting && options.separator) { - return label.replace(sepRegexp, options.separator); + label = label.replace(sepRegexp, options.separator); + } + + if (options.appendWorkspaceSuffix && formatting?.workspaceSuffix) { + label = this.appendWorkspaceSuffix(label, resource); } return label; From f78af5335027855ca7221e4211978034b858c612 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 17 Dec 2024 11:38:12 +0100 Subject: [PATCH 027/200] Fix extra primary button in comments (#236325) Fixes https://github.com/microsoft/vscode-pull-request-github/issues/6553 --- src/vs/workbench/contrib/comments/browser/commentNode.ts | 2 +- src/vs/workbench/contrib/comments/browser/commentReply.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 37958bce7d8b9..f90a81288a3f9 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -658,7 +658,7 @@ export class CommentNode extends Disposable { this._register(menu); this._register(menu.onDidChange(() => { - this._commentEditorActions?.setActions(menu); + this._commentEditorActions?.setActions(menu, true); })); this._commentEditorActions = new CommentFormActions(this.keybindingService, this._contextKeyService, this.contextMenuService, container, (action: IAction): void => { diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index 981a3513d5980..37bb741dc681b 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -300,7 +300,7 @@ export class CommentReply extends Disposable { const editorMenu = this._commentMenus.getCommentEditorActions(this._contextKeyService); this._register(editorMenu); this._register(editorMenu.onDidChange(() => { - this._commentEditorActions.setActions(editorMenu); + this._commentEditorActions.setActions(editorMenu, true); })); this._commentEditorActions = new CommentFormActions(this.keybindingService, this._contextKeyService, this.contextMenuService, container, async (action: IAction) => { From 9b0cd6551590014e8b91e78425848ccb6a12c6e6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Dec 2024 12:22:51 +0100 Subject: [PATCH 028/200] theme - fix some colors in status bar for #235718 (#236331) --- extensions/theme-defaults/themes/hc_light.json | 4 +++- extensions/theme-defaults/themes/light_modern.json | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/extensions/theme-defaults/themes/hc_light.json b/extensions/theme-defaults/themes/hc_light.json index 6d192e7675711..e59494f9cfb79 100644 --- a/extensions/theme-defaults/themes/hc_light.json +++ b/extensions/theme-defaults/themes/hc_light.json @@ -569,6 +569,8 @@ } ], "colors": { - "actionBar.toggledBackground": "#dddddd" + "actionBar.toggledBackground": "#dddddd", + "statusBarItem.remoteBackground": "#FFFFFF", + "statusBarItem.remoteForeground": "#000000" } } diff --git a/extensions/theme-defaults/themes/light_modern.json b/extensions/theme-defaults/themes/light_modern.json index 9b4d627fcd146..7f5e622639c70 100644 --- a/extensions/theme-defaults/themes/light_modern.json +++ b/extensions/theme-defaults/themes/light_modern.json @@ -104,6 +104,8 @@ "statusBar.background": "#F8F8F8", "statusBar.foreground": "#3B3B3B", "statusBar.border": "#E5E5E5", + "statusBarItem.hoverBackground": "#B8B8B850", + "statusBarItem.compactHoverBackground": "#CCCCCC", "statusBar.debuggingBackground": "#FD716C", "statusBar.debuggingForeground": "#000000", "statusBar.focusBorder": "#005FB8", From c269884f97e3ec4e62495ce64dec99d2d1f9db0b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 17 Dec 2024 12:30:48 +0100 Subject: [PATCH 029/200] disable sticky scroll while steaming edits (#236332) disable sticky scroll white steaming edits fixes https://github.com/microsoft/vscode-copilot/issues/11102 --- .../contrib/chat/browser/chatEditorController.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorController.ts b/src/vs/workbench/contrib/chat/browser/chatEditorController.ts index 18a7c1a258ef1..e2a186b5886bc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorController.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorController.ts @@ -11,7 +11,7 @@ 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 { EditorOption, IEditorStickyScrollOptions } 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'; @@ -164,25 +164,30 @@ export class ChatEditorController extends Disposable implements IEditorContribut let actualReadonly: boolean | undefined; let actualDeco: 'off' | 'editable' | 'on' | undefined; + let actualStickyScroll: IEditorStickyScrollOptions | undefined; this._register(autorun(r => { const value = shouldBeReadOnly.read(r); if (value) { actualReadonly ??= this._editor.getOption(EditorOption.readOnly); actualDeco ??= this._editor.getOption(EditorOption.renderValidationDecorations); + actualStickyScroll ??= this._editor.getOption(EditorOption.stickyScroll); this._editor.updateOptions({ readOnly: true, - renderValidationDecorations: 'off' + renderValidationDecorations: 'off', + stickyScroll: { enabled: false } }); } else { - if (actualReadonly !== undefined && actualDeco !== undefined) { + if (actualReadonly !== undefined && actualDeco !== undefined && actualStickyScroll !== undefined) { this._editor.updateOptions({ readOnly: actualReadonly, - renderValidationDecorations: actualDeco + renderValidationDecorations: actualDeco, + stickyScroll: actualStickyScroll }); actualReadonly = undefined; actualDeco = undefined; + actualStickyScroll = undefined; } } })); From f776d0611ec9fc8c8169dc6883ab1adaa7965f24 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 Dec 2024 04:09:17 -0800 Subject: [PATCH 030/200] Fill pwsh commands via Get-Command Fixes #235024 --- .../src/terminalSuggestMain.ts | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 0c29d9688b11c..5071c4c79ce96 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -25,40 +25,44 @@ function getBuiltinCommands(shell: string): string[] | undefined { } const filter = (cmd: string) => cmd; const options: ExecOptionsWithStringEncoding = { encoding: 'utf-8', shell }; + let commands: string[] | undefined; switch (shellType) { case 'bash': { const bashOutput = execSync('compgen -b', options); - const bashResult = bashOutput.split('\n').filter(filter); - if (bashResult.length) { - cachedBuiltinCommands?.set(shellType, bashResult); - return bashResult; - } + commands = bashOutput.split('\n').filter(filter); break; } case 'zsh': { const zshOutput = execSync('printf "%s\\n" ${(k)builtins}', options); - const zshResult = zshOutput.split('\n').filter(filter); - if (zshResult.length) { - cachedBuiltinCommands?.set(shellType, zshResult); - return zshResult; - } + commands = zshOutput.split('\n').filter(filter); + break; } case 'fish': { // TODO: ghost text in the command line prevents // completions from working ATM for fish const fishOutput = execSync('functions -n', options); - const fishResult = fishOutput.split(', ').filter(filter); - if (fishResult.length) { - cachedBuiltinCommands?.set(shellType, fishResult); - return fishResult; - } + commands = fishOutput.split(', ').filter(filter); break; } case 'pwsh': { - // native pwsh completions are builtin to vscode - return []; + const output = execSync('Get-Command | Select-Object Name, CommandType, DisplayName | ConvertTo-Json', options); + let json: any; + try { + json = JSON.parse(output); + } catch (e) { + console.error('Error parsing pwsh output:', e); + return []; + } + // TODO: Return a rich type with kind and detail + commands = (json as any[]).map(e => e.Name); + break; } } + // TODO: Cache failure results too + if (commands?.length) { + cachedBuiltinCommands?.set(shellType, commands); + return commands; + } return; } catch (error) { From cdd41b08dba59afbff4a499f522e35dde0d53caa Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 Dec 2024 04:27:47 -0800 Subject: [PATCH 031/200] Reduce duplication in terminal completion service tests --- .../browser/terminalCompletionService.test.ts | 94 ++++++------------- 1 file changed, 28 insertions(+), 66 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts index 3ce17efc70055..1ce22b4138c9f 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts @@ -6,7 +6,6 @@ import { URI } from '../../../../../../base/common/uri.js'; import { IFileService, IFileStatWithMetadata, IResolveMetadataFileOptions } from '../../../../../../platform/files/common/files.js'; import { TerminalCompletionService, TerminalCompletionItemKind, TerminalResourceRequestConfig } from '../../browser/terminalCompletionService.js'; -import sinon from 'sinon'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; import assert from 'assert'; import { isWindows } from '../../../../../../base/common/platform.js'; @@ -44,10 +43,6 @@ suite('TerminalCompletionService', () => { childResources = []; }); - teardown(() => { - sinon.restore(); - }); - suite('resolveResources should return undefined', () => { test('if cwd is not provided', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { @@ -69,157 +64,124 @@ suite('TerminalCompletionService', () => { }); suite('resolveResources should return folder completions', () => { - test('', async () => { + setup(() => { + validResources = [URI.parse('file:///test')]; + const childFolder = { resource: URI.parse('file:///test/folder1/'), name: 'folder1', isDirectory: true, isFile: false }; + const childFile = { resource: URI.parse('file:///test/file1.txt'), name: 'file1.txt', isDirectory: false, isFile: true }; + childResources = [childFolder, childFile]; + }); + + test('|', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { cwd: URI.parse('file:///test'), foldersRequested: true, pathSeparator }; - validResources = [URI.parse('file:///test')]; - const childFolder = { resource: URI.parse('file:///test/folder1/'), name: 'folder1', isDirectory: true, isFile: false }; - const childFile = { resource: URI.parse('file:///test/file1.txt'), name: 'file1.txt', isDirectory: false, isFile: true }; - childResources = [childFolder, childFile]; const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '', 1); - assert(!!result); - assert(result.length === 1); - assert.deepEqual(result![0], { + assert.deepEqual(result, [{ label: `.${pathSeparator}folder1${pathSeparator}`, kind: TerminalCompletionItemKind.Folder, isDirectory: true, isFile: false, replacementIndex: 1, replacementLength: 1 - }); + }]); }); - test('.', async () => { + test('.|', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { cwd: URI.parse('file:///test'), foldersRequested: true, pathSeparator }; - validResources = [URI.parse('file:///test')]; - const childFolder = { resource: URI.parse('file:///test/folder1/'), name: 'folder1', isDirectory: true, isFile: false }; - const childFile = { resource: URI.parse('file:///test/file1.txt'), name: 'file1.txt', isDirectory: false, isFile: true }; - childResources = [childFolder, childFile]; const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '.', 2); - assert(!!result); - assert(result.length === 1); - assert.deepEqual(result![0], { + assert.deepEqual(result, [{ label: `.${pathSeparator}folder1${pathSeparator}`, kind: TerminalCompletionItemKind.Folder, isDirectory: true, isFile: false, replacementIndex: 1, replacementLength: 1 - }); + }]); }); - test('./', async () => { + test('./|', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { cwd: URI.parse('file:///test'), foldersRequested: true, pathSeparator }; - validResources = [URI.parse('file:///test')]; - const childFolder = { resource: URI.parse('file:///test/folder1/'), name: 'folder1', isDirectory: true, isFile: false }; - const childFile = { resource: URI.parse('file:///test/file1.txt'), name: 'file1.txt', isDirectory: false, isFile: true }; - childResources = [childFolder, childFile]; const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 3); - assert(!!result); - assert(result.length === 1); - assert.deepEqual(result![0], { + assert.deepEqual(result, [{ label: `.${pathSeparator}folder1${pathSeparator}`, kind: TerminalCompletionItemKind.Folder, isDirectory: true, isFile: false, replacementIndex: 1, replacementLength: 2 - }); + }]); }); - test('cd ', async () => { + test('cd |', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { cwd: URI.parse('file:///test'), foldersRequested: true, pathSeparator }; - validResources = [URI.parse('file:///test')]; - const childFolder = { resource: URI.parse('file:///test/folder1/'), name: 'folder1', isDirectory: true, isFile: false }; - const childFile = { resource: URI.parse('file:///test/file1.txt'), name: 'file1.txt', isDirectory: false, isFile: true }; - childResources = [childFolder, childFile]; const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3); - assert(!!result); - assert(result.length === 1); - assert.deepEqual(result![0], { + assert.deepEqual(result, [{ label: `.${pathSeparator}folder1${pathSeparator}`, kind: TerminalCompletionItemKind.Folder, isDirectory: true, isFile: false, replacementIndex: 3, replacementLength: 3 - }); + }]); }); - test('cd .', async () => { + test('cd .|', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { cwd: URI.parse('file:///test'), foldersRequested: true, pathSeparator }; - validResources = [URI.parse('file:///test/')]; - const childFolder = { resource: URI.parse('file:///test/folder1/'), name: 'folder1', isDirectory: true, isFile: false }; - const childFile = { resource: URI.parse('file:///test/file1.txt'), name: 'file1.txt', isDirectory: false, isFile: true }; - childResources = [childFolder, childFile]; const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd .', 4); - assert(!!result); - assert(result.length === 1); - assert.deepEqual(result![0], { + assert.deepEqual(result, [{ label: `.${pathSeparator}folder1${pathSeparator}`, kind: TerminalCompletionItemKind.Folder, isDirectory: true, isFile: false, replacementIndex: 3, replacementLength: 1 // replacing . - }); + }]); }); - test('cd ./', async () => { + test('cd ./|', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { cwd: URI.parse('file:///test'), foldersRequested: true, pathSeparator }; - const childFolder = { resource: URI.parse('file:///test/folder1/'), name: 'folder1', isDirectory: true, isFile: false }; - const childFile = { resource: URI.parse('file:///test/file1.txt'), name: 'file1.txt', isDirectory: false, isFile: true }; - childResources = [childFolder, childFile]; const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ./', 5); - assert(!!result); - assert(result.length === 1); - assert.deepEqual(result![0], { + assert.deepEqual(result, [{ label: `.${pathSeparator}folder1${pathSeparator}`, kind: TerminalCompletionItemKind.Folder, isDirectory: true, isFile: false, replacementIndex: 3, replacementLength: 2 // replacing ./ - }); + }]); }); - test('cd ./f', async () => { + test('cd ./f|', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { cwd: URI.parse('file:///test'), foldersRequested: true, pathSeparator }; - const childFolder = { resource: URI.parse('file:///test/folder1/'), name: 'folder1', isDirectory: true, isFile: false }; - const childFile = { resource: URI.parse('file:///test/file1.txt'), name: 'file1.txt', isDirectory: false, isFile: true }; - childResources = [childFolder, childFile]; const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ./f', 6); - assert(!!result); - assert(result.length === 1); - assert.deepEqual(result![0], { + assert.deepEqual(result, [{ label: `.${pathSeparator}folder1${pathSeparator}`, kind: TerminalCompletionItemKind.Folder, isDirectory: true, isFile: false, replacementIndex: 3, replacementLength: 3 // replacing ./f - }); + }]); }); }); }); From 6997bee2acbcec80320a948d520e388f913f9d04 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:49:21 +0100 Subject: [PATCH 032/200] Make badge smaller, reduce margin and change color when icon (#236346) make badge smaller and closer to label --- .../browser/parts/media/paneCompositePart.css | 12 ++++++------ src/vs/workbench/browser/parts/panel/panelPart.ts | 11 ++++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index 2b4518ca20432..de62593762838 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -183,7 +183,7 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .badge, .monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .badge { - margin-left: 8px; + margin-left: 2px; display: flex; align-items: center; } @@ -196,11 +196,11 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .badge .badge-content, .monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .badge .badge-content { padding: 3px 5px; - border-radius: 11px; - font-size: 11px; - min-width: 18px; - height: 18px; - line-height: 11px; + border-radius: 10px; + font-size: 10px; + min-width: 16px; + height: 16px; + line-height: 10px; font-weight: normal; text-align: center; display: inline-block; diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 4646b4412f3b9..6363f26638469 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -15,8 +15,8 @@ import { IKeybindingService } from '../../../../platform/keybinding/common/keybi import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { TogglePanelAction } from './panelActions.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BORDER } from '../../../common/theme.js'; -import { contrastBorder, badgeBackground, badgeForeground } from '../../../../platform/theme/common/colorRegistry.js'; +import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND } from '../../../common/theme.js'; +import { badgeBackground, badgeForeground, contrastBorder } from '../../../../platform/theme/common/colorRegistry.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { Dimension } from '../../../../base/browser/dom.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -130,12 +130,13 @@ export class PanelPart extends AbstractPaneCompositePart { } protected getCompositeBarOptions(): IPaneCompositeBarOptions { + const showIcons = this.configurationService.getValue('workbench.panel.showLabels') === false; return { partContainerClass: 'panel', pinnedViewContainersKey: 'workbench.panel.pinnedPanels', placeholderViewContainersKey: 'workbench.panel.placeholderPanels', viewContainersWorkspaceStateKey: 'workbench.panel.viewContainersWorkspaceState', - icon: this.configurationService.getValue('workbench.panel.showLabels') === false, + icon: showIcons, orientation: ActionsOrientation.HORIZONTAL, recomputeSizes: true, activityHoverOptions: { @@ -152,8 +153,8 @@ export class PanelPart extends AbstractPaneCompositePart { activeBorderBottomColor: theme.getColor(PANEL_ACTIVE_TITLE_BORDER), activeForegroundColor: theme.getColor(PANEL_ACTIVE_TITLE_FOREGROUND), inactiveForegroundColor: theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND), - badgeBackground: theme.getColor(badgeBackground), - badgeForeground: theme.getColor(badgeForeground), + badgeBackground: theme.getColor(showIcons ? ACTIVITY_BAR_BADGE_BACKGROUND : badgeBackground), + badgeForeground: theme.getColor(showIcons ? ACTIVITY_BAR_BADGE_FOREGROUND : badgeForeground), dragAndDropBorder: theme.getColor(PANEL_DRAG_AND_DROP_BORDER) }) }; From d162ceb7fecc3476f27d75196e3dd71b06c51bc1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 17 Dec 2024 15:15:31 +0100 Subject: [PATCH 033/200] extension events use new `ExtensionError` so that these errors don't make it into "normal" error telemetry (#236336) * extension events use new `ExtensionError` so that these errors don't make it into "normal" error telemetry fixes https://github.com/microsoft/vscode/issues/232914 * fix tests --- .../platform/extensions/common/extensions.ts | 14 +++++++++++ .../workbench/api/common/extHost.api.impl.ts | 5 ++-- .../workbench/api/common/extensionHostMain.ts | 25 +++++++++++++------ .../api/test/common/extensionHostMain.test.ts | 7 ++++-- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index cee0043bad64d..687fd50dc360b 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -447,6 +447,20 @@ export class ExtensionIdentifierMap { } } +/** + * An error that is clearly from an extension, identified by the `ExtensionIdentifier` + */ +export class ExtensionError extends Error { + + readonly extension: ExtensionIdentifier; + + constructor(extensionIdentifier: ExtensionIdentifier, cause: Error, message?: string) { + super(`Error in extension ${ExtensionIdentifier.toKey(extensionIdentifier)}: ${message ?? cause.message}`, { cause }); + this.name = 'ExtensionError'; + this.extension = extensionIdentifier; + } +} + export interface IRelaxedExtensionDescription extends IRelaxedExtensionManifest { id?: string; identifier: ExtensionIdentifier; diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 85ff1aa52b2c3..868906908ea7b 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -14,7 +14,7 @@ import { TextEditorCursorStyle } from '../../../editor/common/config/editorOptio 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 { ExtensionError, 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'; @@ -245,8 +245,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I 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 ExtensionError(extension.identifier, err, 'FAILED to handle event')); } }); disposables?.push(handle); diff --git a/src/vs/workbench/api/common/extensionHostMain.ts b/src/vs/workbench/api/common/extensionHostMain.ts index 6c93b88e428b4..d0180c1a081a5 100644 --- a/src/vs/workbench/api/common/extensionHostMain.ts +++ b/src/vs/workbench/api/common/extensionHostMain.ts @@ -11,7 +11,7 @@ import { IMessagePassingProtocol } from '../../../base/parts/ipc/common/ipc.js'; import { MainContext, MainThreadConsoleShape } from './extHost.protocol.js'; import { IExtensionHostInitData } from '../../services/extensions/common/extensionHostProtocol.js'; import { RPCProtocol } from '../../services/extensions/common/rpcProtocol.js'; -import { ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; +import { ExtensionError, ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { ILogService } from '../../../platform/log/common/log.js'; import { getSingletonServiceDescriptors } from '../../../platform/instantiation/common/extensions.js'; import { ServiceCollection } from '../../../platform/instantiation/common/serviceCollection.js'; @@ -119,15 +119,24 @@ export abstract class ErrorHandler { logService.error(err); const errorData = errors.transformErrorForSerialization(err); - const stackData = extensionErrors.get(err); - if (!stackData?.extensionIdentifier) { - mainThreadErrors.$onUnexpectedError(errorData); - return; + + let extension: ExtensionIdentifier | undefined; + if (err instanceof ExtensionError) { + extension = err.extension; + } else { + const stackData = extensionErrors.get(err); + extension = stackData?.extensionIdentifier; + } + + if (extension) { + mainThreadExtensions.$onExtensionRuntimeError(extension, errorData); + const reported = extensionTelemetry.onExtensionError(extension, err); + logService.trace('forwarded error to extension?', reported, extension); } + }); - mainThreadExtensions.$onExtensionRuntimeError(stackData.extensionIdentifier, errorData); - const reported = extensionTelemetry.onExtensionError(stackData.extensionIdentifier, err); - logService.trace('forwarded error to extension?', reported, stackData); + errors.errorHandler.addListener(err => { + mainThreadErrors.$onUnexpectedError(err); }); } } diff --git a/src/vs/workbench/api/test/common/extensionHostMain.test.ts b/src/vs/workbench/api/test/common/extensionHostMain.test.ts index cda646f07cf19..aa96a67b44547 100644 --- a/src/vs/workbench/api/test/common/extensionHostMain.test.ts +++ b/src/vs/workbench/api/test/common/extensionHostMain.test.ts @@ -14,7 +14,7 @@ import { ExtensionIdentifier, IExtensionDescription } from '../../../../platform import { InstantiationService } from '../../../../platform/instantiation/common/instantiationService.js'; import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; import { ILogService, NullLogService } from '../../../../platform/log/common/log.js'; -import { MainThreadExtensionServiceShape } from '../../common/extHost.protocol.js'; +import { MainThreadErrorsShape, MainThreadExtensionServiceShape } from '../../common/extHost.protocol.js'; import { ExtensionPaths, IExtHostExtensionService } from '../../common/extHostExtensionService.js'; import { IExtHostRpcService } from '../../common/extHostRpcService.js'; import { IExtHostTelemetry } from '../../common/extHostTelemetry.js'; @@ -30,9 +30,12 @@ suite('ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slo } const extensionsIndex = TernarySearchTree.forUris(); - const mainThreadExtensionsService = new class extends mock() { + const mainThreadExtensionsService = new class extends mock() implements MainThreadErrorsShape { override $onExtensionRuntimeError(extensionId: ExtensionIdentifier, data: SerializedError): void { + } + $onUnexpectedError(err: any | SerializedError): void { + } }; From 206033fd3f1cabff14b46e3ffdb3e5c487dd05a3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 17 Dec 2024 15:36:20 +0100 Subject: [PATCH 034/200] drop code lenses with invalid/missing range (#236353) https://github.com/microsoft/vscode/issues/233159 --- src/vs/workbench/api/common/extHostLanguageFeatures.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index cd42bd3bf7754..9783b16e88013 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -130,6 +130,12 @@ class CodeLensAdapter { lenses: [], }; for (let i = 0; i < lenses.length; i++) { + + if (!Range.isRange(lenses[i].range)) { + console.warn('INVALID code lens, range is not defined', this._extension.identifier.value); + continue; + } + result.lenses.push({ cacheId: [cacheId, i], range: typeConvert.Range.from(lenses[i].range), From 317d55da7b91e07131403663d8aa6f9499e3a112 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 17 Dec 2024 16:08:13 +0100 Subject: [PATCH 035/200] Pressing alt/opt makes the hover temporarily sticky (#236356) * add alt key to make hover temporarily sticky * adding code --- .../hover/browser/contentHoverController.ts | 39 ++++++++++++++++--- .../browser/contentHoverWidgetWrapper.ts | 26 ++++++++++--- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHoverController.ts b/src/vs/editor/contrib/hover/browser/contentHoverController.ts index a3f67bb85f63b..eec21e1725975 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverController.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverController.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { DECREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID, SHOW_OR_FOCUS_HOVER_ACTION_ID } from './hoverActionIds.js'; -import { IKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; -import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; +import { IKeyboardEvent, StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; +import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; import { ICodeEditor, IEditorMouseEvent, IPartialEditorMouseEvent } from '../../../browser/editorBrowser.js'; import { ConfigurationChangedEvent, EditorOption } from '../../../common/config/editorOptions.js'; import { Range } from '../../../common/core/range.js'; @@ -22,6 +22,9 @@ import { ContentHoverWidgetWrapper } from './contentHoverWidgetWrapper.js'; import './hover.css'; import { Emitter } from '../../../../base/common/event.js'; import { isOnColorDecorator } from '../../colorPicker/browser/hoverColorPicker/hoverColorPicker.js'; +import { KeyCode } from '../../../../base/common/keyCodes.js'; +import { EventType } from '../../../../base/browser/dom.js'; +import { mainWindow } from '../../../../base/browser/window.js'; // sticky hover widget which doesn't disappear on focus out and such const _sticky = false @@ -92,11 +95,18 @@ export class ContentHoverController extends Disposable implements IEditorContrib this._listenersStore.add(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); this._listenersStore.add(this._editor.onMouseUp(() => this._onEditorMouseUp())); this._listenersStore.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e))); - this._listenersStore.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e))); this._listenersStore.add(this._editor.onMouseLeave((e) => this._onEditorMouseLeave(e))); this._listenersStore.add(this._editor.onDidChangeModel(() => this._cancelSchedulerAndHide())); this._listenersStore.add(this._editor.onDidChangeModelContent(() => this._cancelScheduler())); this._listenersStore.add(this._editor.onDidScrollChange((e: IScrollEvent) => this._onEditorScrollChanged(e))); + const keyDownListener = (e: KeyboardEvent) => this._onKeyDown(e); + const keyUpListener = (e: KeyboardEvent) => this._onKeyUp(e); + mainWindow.addEventListener(EventType.KEY_DOWN, keyDownListener); + mainWindow.addEventListener(EventType.KEY_UP, keyUpListener); + this._listenersStore.add(toDisposable(() => { + mainWindow.removeEventListener(EventType.KEY_DOWN, keyDownListener); + mainWindow.removeEventListener(EventType.KEY_UP, keyUpListener); + })); } private _unhookListeners(): void { @@ -155,6 +165,9 @@ export class ContentHoverController extends Disposable implements IEditorContrib if (_sticky) { return; } + if (this._contentWidget) { + this._contentWidget.temporarilySticky = false; + } this.hideContentHover(); } @@ -234,17 +247,31 @@ export class ContentHoverController extends Disposable implements IEditorContrib this.hideContentHover(); } - private _onKeyDown(e: IKeyboardEvent): void { - if (!this._editor.hasModel()) { + private _onKeyDown(e: KeyboardEvent): void { + if (!this._contentWidget) { return; } - const isPotentialKeyboardShortcut = this._isPotentialKeyboardShortcut(e); + const event = new StandardKeyboardEvent(e); + if (event.keyCode === KeyCode.Alt) { + this._contentWidget.temporarilySticky = true; + } + const isPotentialKeyboardShortcut = this._isPotentialKeyboardShortcut(event); if (isPotentialKeyboardShortcut) { return; } this.hideContentHover(); } + private _onKeyUp(e: KeyboardEvent): void { + if (!this._contentWidget) { + return; + } + const event = new StandardKeyboardEvent(e); + if (event.keyCode === KeyCode.Alt) { + this._contentWidget.temporarilySticky = false; + } + } + private _isPotentialKeyboardShortcut(e: IKeyboardEvent): boolean { if (!this._editor.hasModel() || !this._contentWidget) { return false; diff --git a/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts b/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts index ad9a992096707..228e9533d299d 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts @@ -27,6 +27,7 @@ export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidge private _currentResult: ContentHoverResult | null = null; private _renderedContentHover: RenderedContentHover | undefined; + private _temporarilySticky: boolean = false; private readonly _contentHoverWidget: ContentHoverWidget; private readonly _participants: IEditorHoverParticipant[]; @@ -162,11 +163,25 @@ export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidge if (currentHoverResultIsEmpty) { currentHoverResult = null; } + const hoverVisible = this._contentHoverWidget.isVisible; + if (!hoverVisible) { + this._renderResult(currentHoverResult); + } else { + if (this._temporarilySticky) { + return; + } else { + this._renderResult(currentHoverResult); + } + } + } + + private _renderResult(currentHoverResult: ContentHoverResult | null): void { this._currentResult = currentHoverResult; if (this._currentResult) { this._showHover(this._currentResult); } else { - this._hideHover(); + this._contentHoverWidget.hide(); + this._participants.forEach(participant => participant.handleHide?.()); } } @@ -215,11 +230,6 @@ export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidge } } - private _hideHover(): void { - this._contentHoverWidget.hide(); - this._participants.forEach(participant => participant.handleHide?.()); - } - private _getHoverContext(): IEditorHoverContext { const hide = () => { this.hide(); @@ -293,6 +303,10 @@ export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidge } } + public set temporarilySticky(value: boolean) { + this._temporarilySticky = value; + } + public startShowingAtRange(range: Range, mode: HoverStartMode, source: HoverStartSource, focus: boolean): void { this._startShowingOrUpdateHover(new HoverRangeAnchor(0, range, undefined, undefined), mode, source, focus, null); } From 538412ba9def08421a42e7880bdbd31803dbb88c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:15:26 +0100 Subject: [PATCH 036/200] Diff - manually revert 854108f7d2a8b40aa1ac0390f95088e61153ebc5 (#236355) --- .../diffEditor/components/diffEditorEditors.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts index 5d8f652a48565..638c9fe8bd5b8 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts @@ -19,6 +19,7 @@ import { localize } from '../../../../../nls.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; import { DiffEditorOptions } from '../diffEditorOptions.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; export class DiffEditorEditors extends Disposable { public readonly original = this._register(this._createLeftHandSideEditor(this._options.editorOptions.get(), this._argCodeEditorWidgetOptions.originalEditor || {})); @@ -51,6 +52,7 @@ export class DiffEditorEditors extends Disposable { private readonly _options: DiffEditorOptions, private _argCodeEditorWidgetOptions: IDiffCodeEditorWidgetOptions, private readonly _createInnerEditor: (instantiationService: IInstantiationService, container: HTMLElement, options: Readonly, editorWidgetOptions: ICodeEditorWidgetOptions) => CodeEditorWidget, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IKeybindingService private readonly _keybindingService: IKeybindingService ) { @@ -80,14 +82,22 @@ export class DiffEditorEditors extends Disposable { private _createLeftHandSideEditor(options: Readonly, codeEditorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget { const leftHandSideOptions = this._adjustOptionsForLeftHandSide(undefined, options); const editor = this._constructInnerEditor(this._instantiationService, this.originalEditorElement, leftHandSideOptions, codeEditorWidgetOptions); - editor.setContextValue('isInDiffLeftEditor', true); + + const isInDiffLeftEditorKey = this._contextKeyService.createKey('isInDiffLeftEditor', editor.hasWidgetFocus()); + this._register(editor.onDidFocusEditorWidget(() => isInDiffLeftEditorKey.set(true))); + this._register(editor.onDidBlurEditorWidget(() => isInDiffLeftEditorKey.set(false))); + return editor; } private _createRightHandSideEditor(options: Readonly, codeEditorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget { const rightHandSideOptions = this._adjustOptionsForRightHandSide(undefined, options); const editor = this._constructInnerEditor(this._instantiationService, this.modifiedEditorElement, rightHandSideOptions, codeEditorWidgetOptions); - editor.setContextValue('isInDiffRightEditor', true); + + const isInDiffRightEditorKey = this._contextKeyService.createKey('isInDiffRightEditor', editor.hasWidgetFocus()); + this._register(editor.onDidFocusEditorWidget(() => isInDiffRightEditorKey.set(true))); + this._register(editor.onDidBlurEditorWidget(() => isInDiffRightEditorKey.set(false))); + return editor; } From 0acab1b9b56310c5578ef540a9028a53be98af09 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 17 Dec 2024 16:20:39 +0100 Subject: [PATCH 037/200] Fixes #11380 (#236352) Adjusts zIndex. Fixes microsoft/vscode-copilot#11380 --- .../browser/view/inlineEdits/sideBySideDiff.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts index b2cd7d9ef5689..d36ae86f08529 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts @@ -601,7 +601,7 @@ export class InlineEditsSideBySideDiff extends Disposable { class: 'inline-edits-view', style: { overflow: 'visible', - zIndex: '100', + zIndex: '10', display: this._display, }, }, [ From f76df5bbe6c21bccd7a666043c05f68ac6d32dbd Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 17 Dec 2024 16:24:47 +0100 Subject: [PATCH 038/200] Fixes #11376 (#236358) --- .../browser/view/inlineEdits/sideBySideDiff.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts index d36ae86f08529..0df68982f14b9 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts @@ -412,7 +412,7 @@ export class InlineEditsSideBySideDiff extends Disposable { const codeEditDist = codeEditDistRange.clip(dist); const editHeight = this._editor.getOption(EditorOption.lineHeight) * inlineEdit.modifiedLineRange.length; - const previewEditorWidth = remainingWidthRightOfEditor + editorLayout.width - editorLayout.contentLeft - codeEditDist; + const previewEditorWidth = Math.min(previewContentWidth, remainingWidthRightOfEditor + editorLayout.width - editorLayout.contentLeft - codeEditDist); const edit1 = new Point(left + codeEditDist, selectionTop); const edit2 = new Point(left + codeEditDist, selectionTop + editHeight); From 92965da2b8f50ef4f3109ca2d1c58f26d7bff79c Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 17 Dec 2024 16:29:06 +0100 Subject: [PATCH 039/200] When tab is pressed when content widget is focused, do not hide the hover (#236360) when tab is pressed when content widget is focused, do not hide the hover --- src/vs/editor/contrib/hover/browser/contentHoverController.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/editor/contrib/hover/browser/contentHoverController.ts b/src/vs/editor/contrib/hover/browser/contentHoverController.ts index eec21e1725975..09cb6efcab476 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverController.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverController.ts @@ -259,6 +259,9 @@ export class ContentHoverController extends Disposable implements IEditorContrib if (isPotentialKeyboardShortcut) { return; } + if (this._contentWidget.isFocused && event.keyCode === KeyCode.Tab) { + return; + } this.hideContentHover(); } From 36d8719a8e34fd831ace3960d63d35e320b9b92b Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:37:16 +0100 Subject: [PATCH 040/200] SCM Input - respect `editor.emptySelectionClipboard` setting (#236361) --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index fd25da9f13c1f..8a05bb6c06f7e 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1477,6 +1477,7 @@ class SCMInputWidgetEditorOptions { e => { return e.affectsConfiguration('editor.accessibilitySupport') || e.affectsConfiguration('editor.cursorBlinking') || + e.affectsConfiguration('editor.emptySelectionClipboard') || e.affectsConfiguration('editor.fontFamily') || e.affectsConfiguration('editor.rulers') || e.affectsConfiguration('editor.wordWrap') || @@ -1490,21 +1491,14 @@ class SCMInputWidgetEditorOptions { } getEditorConstructionOptions(): IEditorConstructionOptions { - const fontFamily = this._getEditorFontFamily(); - const fontSize = this._getEditorFontSize(); - const lineHeight = this._getEditorLineHeight(fontSize); - return { ...getSimpleEditorOptions(this.configurationService), - ...this._getEditorLanguageConfiguration(), + ...this.getEditorOptions(), cursorWidth: 1, dragAndDrop: true, dropIntoEditor: { enabled: true }, - fontFamily: fontFamily, - fontSize: fontSize, formatOnType: true, lineDecorationsWidth: 6, - lineHeight: lineHeight, overflowWidgetsDomNode: this.overflowWidgetsDomNode, padding: { top: 2, bottom: 2 }, quickSuggestions: false, @@ -1524,8 +1518,9 @@ class SCMInputWidgetEditorOptions { 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 emptySelectionClipboard = this.configurationService.getValue('editor.emptySelectionClipboard') === true; - return { ...this._getEditorLanguageConfiguration(), accessibilitySupport, cursorBlinking, fontFamily, fontSize, lineHeight }; + return { ...this._getEditorLanguageConfiguration(), accessibilitySupport, cursorBlinking, fontFamily, fontSize, lineHeight, emptySelectionClipboard }; } private _getEditorFontFamily(): string { From 422927459007119b4c5bde6dea39b34eefb314ae Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 17 Dec 2024 16:49:21 +0100 Subject: [PATCH 041/200] Fixes https://github.com/microsoft/vscode-copilot/issues/11377 (#236362) --- .../view/inlineEdits/sideBySideDiff.ts | 21 ++++++++++++++++--- .../browser/view/inlineEdits/utils.ts | 9 +++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts index 0df68982f14b9..d58c42f83f962 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts @@ -8,7 +8,7 @@ import { IAction } from '../../../../../../base/common/actions.js'; import { Color } from '../../../../../../base/common/color.js'; import { structuralEquals } from '../../../../../../base/common/equals.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import { IObservable, autorun, constObservable, derived, derivedOpts, observableFromEvent, observableValue } from '../../../../../../base/common/observable.js'; +import { IObservable, autorun, constObservable, derived, derivedObservableWithCache, derivedOpts, observableFromEvent, observableValue } from '../../../../../../base/common/observable.js'; import { MenuId, MenuItemAction } from '../../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../../platform/commands/common/commands.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; @@ -335,6 +335,22 @@ export class InlineEditsSideBySideDiff extends Disposable { private readonly _originalVerticalStartPosition = this._editorObs.observePosition(this._originalStartPosition, this._store).map(p => p?.y); private readonly _originalVerticalEndPosition = this._editorObs.observePosition(this._originalEndPosition, this._store).map(p => p?.y); + private readonly _originalDisplayRange = this._uiState.map(s => s?.originalDisplayRange); + private readonly _editorMaxContentWidthInRange = derived(this, reader => { + const originalDisplayRange = this._originalDisplayRange.read(reader); + if (!originalDisplayRange) { + return constObservable(0); + } + this._editorObs.versionId.read(reader); + + // Take the max value that we observed. + // Reset when either the edit changes or the editor text version. + return derivedObservableWithCache(this, (reader, lastValue) => { + const maxWidth = maxContentWidthInRange(this._editorObs, originalDisplayRange, reader); + return Math.max(maxWidth, lastValue ?? 0); + }); + }).map((v, r) => v.read(r)); + /** * ![test](./layout.dio.svg) */ @@ -352,7 +368,7 @@ export class InlineEditsSideBySideDiff extends Disposable { const horizontalScrollOffset = this._editorObs.scrollLeft.read(reader); - const editorContentMaxWidthInRange = maxContentWidthInRange(this._editorObs, state.originalDisplayRange, reader); + const editorContentMaxWidthInRange = this._editorMaxContentWidthInRange.read(reader); const editorLayout = this._editorObs.layoutInfo.read(reader); const previewContentWidth = this._previewEditorWidth.read(reader); const editorContentAreaWidth = editorLayout.contentWidth - editorLayout.verticalScrollbarWidth; @@ -389,7 +405,6 @@ export class InlineEditsSideBySideDiff extends Disposable { } else { desiredPreviewEditorScrollLeft = horizontalScrollOffset - previewEditorLeftInTextArea; left = editorLayout.contentLeft; - } const selectionTop = this._originalVerticalStartPosition.read(reader) ?? this._editor.getTopForLineNumber(range.startLineNumber) - this._editorObs.scrollTop.read(reader); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts index af375cca81675..d9928a05b2eb0 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts @@ -16,6 +16,7 @@ import { URI } from '../../../../../../base/common/uri.js'; import { MenuEntryActionViewItem } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { ObservableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; import { Point } from '../../../../../browser/point.js'; +import { EditorOption } from '../../../../../common/config/editorOptions.js'; import { LineRange } from '../../../../../common/core/lineRange.js'; import { OffsetRange } from '../../../../../common/core/offsetRange.js'; import { Position } from '../../../../../common/core/position.js'; @@ -34,7 +35,13 @@ export function maxContentWidthInRange(editor: ObservableCodeEditor, range: Line editor.scrollTop.read(reader); for (let i = range.startLineNumber; i < range.endLineNumberExclusive; i++) { const column = model.getLineMaxColumn(i); - const lineContentWidth = editor.editor.getOffsetForColumn(i, column); + let lineContentWidth = editor.editor.getOffsetForColumn(i, column); + if (lineContentWidth === -1) { + // approximation + const typicalHalfwidthCharacterWidth = editor.editor.getOption(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; + const approximation = column * typicalHalfwidthCharacterWidth; + lineContentWidth = approximation; + } maxContentWidth = Math.max(maxContentWidth, lineContentWidth); } const lines = range.mapToLineArray(l => model.getLineContent(l)); From 68410e1431f9849547b67d0b38dbc76cdb4aa3c8 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 17 Dec 2024 17:03:46 +0100 Subject: [PATCH 042/200] Fixes https://github.com/microsoft/vscode-copilot/issues/9375 (#236363) --- .../browser/model/inlineCompletionsModel.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index 65201933d14e2..b2669bdf25f19 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -227,6 +227,7 @@ export class InlineCompletionsModel extends Disposable { this._onlyRequestInlineEditsSignal.trigger(tx); } this._isActive.set(true, tx); + this._inAcceptFlow.set(true, tx); this._forceUpdateExplicitlySignal.trigger(tx); }); await this._fetchInlineCompletionsPromise.get(); @@ -480,6 +481,10 @@ export class InlineCompletionsModel extends Disposable { }); private readonly _tabShouldIndent = derived(this, reader => { + if (this._inAcceptFlow.read(reader)) { + return false; + } + function isMultiLine(range: Range): boolean { return range.startLineNumber !== range.endLineNumber; } From 639c4c8d1e63eb05f4061202e65311ec6ea4a940 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:57:23 +0100 Subject: [PATCH 043/200] Make title bar and command center visible when actions are enabled (#236369) Make title bar and or command center visible when actions are enabled in title bar --- src/vs/workbench/browser/layout.ts | 37 ++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index b60d286428356..84e2eaadb3c34 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -116,12 +116,18 @@ interface IInitialEditorsState { readonly layout?: EditorGroupLayout; } +const COMMAND_CENTER_SETTINGS = [ + 'chat.commandCenter.enabled', + 'workbench.navigationControl.enabled', + 'workbench.experimental.share.enabled', +]; + export const TITLE_BAR_SETTINGS = [ LayoutSettings.ACTIVITY_BAR_LOCATION, LayoutSettings.COMMAND_CENTER, + ...COMMAND_CENTER_SETTINGS, LayoutSettings.EDITOR_ACTIONS_LOCATION, LayoutSettings.LAYOUT_ACTIONS, - 'workbench.navigationControl.enabled', 'window.menuBarVisibility', TitleBarSetting.TITLE_BAR_STYLE, TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, @@ -355,18 +361,31 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION, LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE, ].some(setting => e.affectsConfiguration(setting))) { - // Show Custom TitleBar if actions moved to the titlebar - const editorActionsMovedToTitlebar = e.affectsConfiguration(LayoutSettings.EDITOR_ACTIONS_LOCATION) && this.configurationService.getValue(LayoutSettings.EDITOR_ACTIONS_LOCATION) === EditorActionsLocation.TITLEBAR; - - let activityBarMovedToTopOrBottom = false; - if (e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION)) { - const activityBarPosition = this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION); - activityBarMovedToTopOrBottom = activityBarPosition === ActivityBarPosition.TOP || activityBarPosition === ActivityBarPosition.BOTTOM; + // Show Command Center if command center actions enabled + const shareEnabled = e.affectsConfiguration('workbench.experimental.share.enabled') && this.configurationService.getValue('workbench.experimental.share.enabled'); + const navigationControlEnabled = e.affectsConfiguration('workbench.navigationControl.enabled') && this.configurationService.getValue('workbench.navigationControl.enabled'); + + // Currently not supported for "chat.commandCenter.enabled" as we + // programatically set this during setup and could lead to unwanted titlebar appearing + // const chatControlsEnabled = e.affectsConfiguration('chat.commandCenter.enabled') && this.configurationService.getValue('chat.commandCenter.enabled'); + + if (shareEnabled || navigationControlEnabled) { + if (this.configurationService.getValue(LayoutSettings.COMMAND_CENTER) === false) { + this.configurationService.updateValue(LayoutSettings.COMMAND_CENTER, true); + return; // onDidChangeConfiguration will be triggered again + } } - if (activityBarMovedToTopOrBottom || editorActionsMovedToTitlebar) { + // Show Custom TitleBar if actions enabled in (or moved to) the titlebar + const editorActionsMovedToTitlebar = e.affectsConfiguration(LayoutSettings.EDITOR_ACTIONS_LOCATION) && this.configurationService.getValue(LayoutSettings.EDITOR_ACTIONS_LOCATION) === EditorActionsLocation.TITLEBAR; + const commandCenterEnabled = e.affectsConfiguration(LayoutSettings.COMMAND_CENTER) && this.configurationService.getValue(LayoutSettings.COMMAND_CENTER); + const layoutControlsEnabled = e.affectsConfiguration(LayoutSettings.LAYOUT_ACTIONS) && this.configurationService.getValue(LayoutSettings.LAYOUT_ACTIONS); + const activityBarMovedToTopOrBottom = e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION) && [ActivityBarPosition.TOP, ActivityBarPosition.BOTTOM].includes(this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION)); + + if (activityBarMovedToTopOrBottom || editorActionsMovedToTitlebar || commandCenterEnabled || layoutControlsEnabled) { if (this.configurationService.getValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY) === CustomTitleBarVisibility.NEVER) { this.configurationService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.AUTO); + return; // onDidChangeConfiguration will be triggered again } } From b4c9953da47ea1a2061397303db782bd67040fb7 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 17 Dec 2024 11:10:39 -0600 Subject: [PATCH 044/200] request folders/files when appropriate (#236370) fix #236368 --- .../src/terminalSuggestMain.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 5071c4c79ce96..4acd5b59f8a00 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -328,16 +328,6 @@ export async function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalCon } } } - const shouldShowCommands = !terminalContext.commandLine.substring(0, terminalContext.cursorPosition).trimStart().includes(' '); - if (shouldShowCommands && (filesRequested === foldersRequested)) { - // Include builitin/available commands in the results - const labels = new Set(items.map(i => i.label)); - for (const command of availableCommands) { - if (!labels.has(command)) { - items.push(createCompletionItem(terminalContext.cursorPosition, prefix, command)); - } - } - } const shouldShowResourceCompletions = ( @@ -351,6 +341,17 @@ export async function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalCon // and neither files nor folders are going to be requested (for a specific spec's argument) && (!filesRequested && !foldersRequested); + const shouldShowCommands = !terminalContext.commandLine.substring(0, terminalContext.cursorPosition).trimStart().includes(' '); + if (shouldShowCommands && (filesRequested === foldersRequested)) { + // Include builitin/available commands in the results + const labels = new Set(items.map(i => i.label)); + for (const command of availableCommands) { + if (!labels.has(command)) { + items.push(createCompletionItem(terminalContext.cursorPosition, prefix, command)); + } + } + } + if (shouldShowResourceCompletions) { filesRequested = true; foldersRequested = true; From d12587c914bebf0e7e878ed9d7eeb5d4fbd9d7cd Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Dec 2024 09:24:26 -0800 Subject: [PATCH 045/200] Fix windows test --- .../test/browser/services/decorationRenderOptions.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts index 9608d1d779a46..ef5cd93a8876a 100644 --- a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts +++ b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts @@ -125,7 +125,7 @@ suite('Decoration Render Options', () => { if (platform.isWindows) { // windows file path (used as string) s.registerDecorationType('test', 'example', { gutterIconPath: URI.file('c:\\files\\miles\\more.png') }); - assertBackground('file:///c:/files/miles/more.png', 'vscode-file://vscode-app/c:/files/miles/more.png'); + assertBackground(CSS.escape('file:///c:/files/miles/more.png'), CSS.escape('vscode-file://vscode-app/c:/files/miles/more.png')); s.removeDecorationType('example'); // single quote must always be escaped/encoded From da1d8b9c8e1daaa9c9b7c4bd86590e7d374d3f29 Mon Sep 17 00:00:00 2001 From: Parasaran Date: Tue, 17 Dec 2024 22:57:12 +0530 Subject: [PATCH 046/200] fix 235221: Passing the markdown content to the webview via meta tag and purifying it before use --- extensions/markdown-language-features/preview-src/index.ts | 6 ++++++ .../src/preview/documentRenderer.ts | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index 33c84e0a38477..d7fa1f18d769b 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -353,6 +353,12 @@ document.addEventListener('click', event => { } }, true); +window.addEventListener('load', () => { + const htmlParser = new DOMParser(); + const markDownHtml = htmlParser.parseFromString(getData('data-md-content'), 'text/html'); + document.body.appendChild(markDownHtml.body); +}); + window.addEventListener('scroll', throttle(() => { updateScrollProgress(); diff --git a/extensions/markdown-language-features/src/preview/documentRenderer.ts b/extensions/markdown-language-features/src/preview/documentRenderer.ts index 331fb5566a0cf..f2447532e002d 100644 --- a/extensions/markdown-language-features/src/preview/documentRenderer.ts +++ b/extensions/markdown-language-features/src/preview/documentRenderer.ts @@ -98,13 +98,13 @@ export class MdDocumentRenderer { + data-state="${escapeAttribute(JSON.stringify(state || {}))}" + data-md-content="${escapeAttribute(JSON.stringify(body.html))}"> ${this._getStyles(resourceProvider, sourceUri, config, imageInfo)} - ${body.html} ${this._getScripts(resourceProvider, nonce)} `; From 330ab6c2928963dd83a7d90a271d9f2eb8db90d2 Mon Sep 17 00:00:00 2001 From: Tony <68118705+Legend-Master@users.noreply.github.com> Date: Wed, 18 Dec 2024 01:50:35 +0800 Subject: [PATCH 047/200] Reland fix custom task shell doesn't work without manually passing in "run command" arg/flag (#236058) --- src/vs/platform/terminal/common/terminal.ts | 1 + .../tasks/browser/terminalTaskSystem.ts | 33 +++++++++++-------- .../browser/terminalProfileResolverService.ts | 1 + 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index c638fb625a20a..8235e8a0ab40c 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -871,6 +871,7 @@ export interface ITerminalProfile { overrideName?: boolean; color?: string; icon?: ThemeIcon | URI | { light: URI; dark: URI }; + isAutomationShell?: boolean; } export interface ITerminalDimensionsOverride extends Readonly { diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index e44eaa47cf96c..3b2c1e51c7f07 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -1112,7 +1112,6 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { color: task.configurationProperties.icon?.color || undefined, waitOnExit }; - let shellSpecified: boolean = false; const shellOptions: IShellConfiguration | undefined = task.command.options && task.command.options.shell; if (shellOptions) { if (shellOptions.executable) { @@ -1121,12 +1120,12 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { shellLaunchConfig.args = undefined; } shellLaunchConfig.executable = await this._resolveVariable(variableResolver, shellOptions.executable); - shellSpecified = true; } if (shellOptions.args) { shellLaunchConfig.args = await this._resolveVariables(variableResolver, shellOptions.args.slice()); } } + const taskShellArgsSpecified = (defaultProfile.isAutomationShell ? shellLaunchConfig.args : shellOptions?.args) !== undefined; if (shellLaunchConfig.args === undefined) { shellLaunchConfig.args = []; } @@ -1139,29 +1138,34 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { windowsShellArgs = true; // If we don't have a cwd, then the terminal uses the home dir. const userHome = await this._pathService.userHome(); - if (basename === 'cmd.exe' && ((options.cwd && isUNC(options.cwd)) || (!options.cwd && isUNC(userHome.fsPath)))) { - return undefined; - } - if ((basename === 'powershell.exe') || (basename === 'pwsh.exe')) { - if (!shellSpecified) { + if (basename === 'cmd.exe') { + if ((options.cwd && isUNC(options.cwd)) || (!options.cwd && isUNC(userHome.fsPath))) { + return undefined; + } + if (!taskShellArgsSpecified) { + toAdd.push('/d', '/c'); + } + } else if ((basename === 'powershell.exe') || (basename === 'pwsh.exe')) { + if (!taskShellArgsSpecified) { toAdd.push('-Command'); } } else if ((basename === 'bash.exe') || (basename === 'zsh.exe')) { windowsShellArgs = false; - if (!shellSpecified) { + if (!taskShellArgsSpecified) { toAdd.push('-c'); } } else if (basename === 'wsl.exe') { - if (!shellSpecified) { + if (!taskShellArgsSpecified) { toAdd.push('-e'); } } else { - if (!shellSpecified) { - toAdd.push('/d', '/c'); + if (!taskShellArgsSpecified) { + // Push `-c` for unknown shells if the user didn't specify the args + toAdd.push('-c'); } } } else { - if (!shellSpecified) { + if (!taskShellArgsSpecified) { // Under Mac remove -l to not start it as a login shell. if (platform === Platform.Platform.Mac) { // Background on -l on osx https://github.com/microsoft/vscode/issues/107563 @@ -1268,11 +1272,12 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { const combinedShellArgs: string[] = Objects.deepClone(configuredShellArgs); shellCommandArgs.forEach(element => { const shouldAddShellCommandArg = configuredShellArgs.every((arg, index) => { - if ((arg.toLowerCase() === element) && (configuredShellArgs.length > index + 1)) { + const isDuplicated = arg.toLowerCase() === element.toLowerCase(); + if (isDuplicated && (configuredShellArgs.length > index + 1)) { // We can still add the argument, but only if not all of the following arguments begin with "-". return !configuredShellArgs.slice(index + 1).every(testArg => testArg.startsWith('-')); } else { - return arg.toLowerCase() !== element; + return !isDuplicated; } }); if (shouldAddShellCommandArg) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts index 6208cfc0413c5..e54a12a1b44d8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts @@ -268,6 +268,7 @@ export abstract class BaseTerminalProfileResolverService extends Disposable impl const automationProfile = this._configurationService.getValue(`terminal.integrated.automationProfile.${this._getOsKey(options.os)}`); if (this._isValidAutomationProfile(automationProfile, options.os)) { automationProfile.icon = this._getCustomIcon(automationProfile.icon) || Codicon.tools; + automationProfile.isAutomationShell = true; return automationProfile; } From ca721227517f2ad0f737f0fde18df588beb96c67 Mon Sep 17 00:00:00 2001 From: Parasaran Date: Tue, 17 Dec 2024 23:32:42 +0530 Subject: [PATCH 048/200] fix 235221: Encode and decode markdown content to escape illegal chars --- extensions/markdown-language-features/preview-src/index.ts | 5 ++++- .../src/preview/documentRenderer.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index d7fa1f18d769b..336245a1fdfd9 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -355,7 +355,10 @@ document.addEventListener('click', event => { window.addEventListener('load', () => { const htmlParser = new DOMParser(); - const markDownHtml = htmlParser.parseFromString(getData('data-md-content'), 'text/html'); + const markDownHtml = htmlParser.parseFromString( + decodeURIComponent(getData('data-md-content')), + 'text/html' + ); document.body.appendChild(markDownHtml.body); }); diff --git a/extensions/markdown-language-features/src/preview/documentRenderer.ts b/extensions/markdown-language-features/src/preview/documentRenderer.ts index f2447532e002d..13e709c765f0c 100644 --- a/extensions/markdown-language-features/src/preview/documentRenderer.ts +++ b/extensions/markdown-language-features/src/preview/documentRenderer.ts @@ -99,7 +99,7 @@ export class MdDocumentRenderer { data-settings="${escapeAttribute(JSON.stringify(initialData))}" data-strings="${escapeAttribute(JSON.stringify(previewStrings))}" data-state="${escapeAttribute(JSON.stringify(state || {}))}" - data-md-content="${escapeAttribute(JSON.stringify(body.html))}"> + data-md-content="${escapeAttribute(JSON.stringify(encodeURIComponent(body.html)))}"> ${this._getStyles(resourceProvider, sourceUri, config, imageInfo)} From dfa26a2c5698f8bb8b0bb504d8cbc0207762dc4b Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 17 Dec 2024 19:39:48 +0100 Subject: [PATCH 049/200] Adjusts zIndex for notebooks. (#236380) --- .../browser/view/inlineEdits/sideBySideDiff.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts index d58c42f83f962..a64c52dcd90cc 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts @@ -616,7 +616,7 @@ export class InlineEditsSideBySideDiff extends Disposable { class: 'inline-edits-view', style: { overflow: 'visible', - zIndex: '10', + zIndex: '20', display: this._display, }, }, [ From 625bae23758002b62954fd10b53d25409421d718 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 Dec 2024 19:44:54 +0100 Subject: [PATCH 050/200] debt: clean up obsolete file usage (#236379) - remove it from scanner, with profiles reading this file is not needed - rename it usage for removal in management service --- .../common/extensionManagement.ts | 4 +- .../common/extensionsScannerService.ts | 51 ++---- .../node/extensionManagementService.ts | 163 +++++++++--------- .../node/extensionsWatcher.ts | 20 +-- .../node/extensionsScannerService.test.ts | 33 +--- 5 files changed, 115 insertions(+), 156 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 155079831fcc7..f08b46ae65b30 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -456,8 +456,8 @@ export const enum ExtensionManagementErrorCode { Extract = 'Extract', Scanning = 'Scanning', ScanningExtension = 'ScanningExtension', - ReadUninstalled = 'ReadUninstalled', - UnsetUninstalled = 'UnsetUninstalled', + ReadRemoved = 'ReadRemoved', + UnsetRemoved = 'UnsetRemoved', Delete = 'Delete', Rename = 'Rename', IntializeDefaultProfile = 'IntializeDefaultProfile', diff --git a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts index 99868cddb5d63..a186b6fa04567 100644 --- a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts @@ -7,7 +7,6 @@ import { coalesce } from '../../../base/common/arrays.js'; import { ThrottledDelayer } from '../../../base/common/async.js'; import * as objects from '../../../base/common/objects.js'; import { VSBuffer } from '../../../base/common/buffer.js'; -import { IStringDictionary } from '../../../base/common/collections.js'; import { getErrorMessage } from '../../../base/common/errors.js'; import { getNodeType, parse, ParseError } from '../../../base/common/json.js'; import { getParseErrorMessage } from '../../../base/common/jsonErrorMessages.js'; @@ -18,12 +17,11 @@ import * as platform from '../../../base/common/platform.js'; import { basename, isEqual, joinPath } from '../../../base/common/resources.js'; import * as semver from '../../../base/common/semver/semver.js'; import Severity from '../../../base/common/severity.js'; -import { isEmptyObject } from '../../../base/common/types.js'; import { URI } from '../../../base/common/uri.js'; import { localize } from '../../../nls.js'; import { IEnvironmentService } from '../../environment/common/environment.js'; import { IProductVersion, Metadata } from './extensionManagement.js'; -import { areSameExtensions, computeTargetPlatform, ExtensionKey, getExtensionId, getGalleryExtensionId } from './extensionManagementUtil.js'; +import { areSameExtensions, computeTargetPlatform, getExtensionId, getGalleryExtensionId } from './extensionManagementUtil.js'; import { ExtensionType, ExtensionIdentifier, IExtensionManifest, TargetPlatform, IExtensionIdentifier, IRelaxedExtensionManifest, UNDEFINED_PUBLISHER, IExtensionDescription, BUILTIN_MANIFEST_CACHE_FILE, USER_MANIFEST_CACHE_FILE, ExtensionIdentifierMap, parseEnabledApiProposalNames } from '../../extensions/common/extensions.js'; import { validateExtensionManifest } from '../../extensions/common/extensionValidator.js'; import { FileOperationResult, IFileService, toFileOperationResult } from '../../files/common/files.js'; @@ -106,7 +104,6 @@ export type ScanOptions = { readonly profileLocation?: URI; readonly includeInvalid?: boolean; readonly includeAllVersions?: boolean; - readonly includeUninstalled?: boolean; readonly checkControlFile?: boolean; readonly language?: string; readonly useCache?: boolean; @@ -145,10 +142,9 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem private readonly _onDidChangeCache = this._register(new Emitter()); readonly onDidChangeCache = this._onDidChangeCache.event; - private readonly obsoleteFile = joinPath(this.userExtensionsLocation, '.obsolete'); - private readonly systemExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, this.currentProfile, this.obsoleteFile)); - private readonly userExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, this.currentProfile, this.obsoleteFile)); - private readonly extensionsScanner = this._register(this.instantiationService.createInstance(ExtensionsScanner, this.obsoleteFile)); + private readonly systemExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, this.currentProfile)); + private readonly userExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, this.currentProfile)); + private readonly extensionsScanner = this._register(this.instantiationService.createInstance(ExtensionsScanner)); constructor( readonly systemExtensionsLocation: URI, @@ -199,8 +195,8 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem const location = scanOptions.profileLocation ?? this.userExtensionsLocation; this.logService.trace('Started scanning user extensions', location); const profileScanOptions: IProfileExtensionsScanOptions | undefined = this.uriIdentityService.extUri.isEqual(scanOptions.profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource) ? { bailOutWhenFileNotFound: true } : undefined; - const extensionsScannerInput = await this.createExtensionScannerInput(location, !!scanOptions.profileLocation, ExtensionType.User, !scanOptions.includeUninstalled, scanOptions.language, true, profileScanOptions, scanOptions.productVersion ?? this.getProductVersion()); - const extensionsScanner = scanOptions.useCache && !extensionsScannerInput.devMode && extensionsScannerInput.excludeObsolete ? this.userExtensionsCachedScanner : this.extensionsScanner; + const extensionsScannerInput = await this.createExtensionScannerInput(location, !!scanOptions.profileLocation, ExtensionType.User, scanOptions.language, true, profileScanOptions, scanOptions.productVersion ?? this.getProductVersion()); + const extensionsScanner = scanOptions.useCache && !extensionsScannerInput.devMode ? this.userExtensionsCachedScanner : this.extensionsScanner; let extensions: IRelaxedScannedExtension[]; try { extensions = await extensionsScanner.scanExtensions(extensionsScannerInput); @@ -221,7 +217,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem if (this.environmentService.isExtensionDevelopment && this.environmentService.extensionDevelopmentLocationURI) { const extensions = (await Promise.all(this.environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file) .map(async extensionDevelopmentLocationURI => { - const input = await this.createExtensionScannerInput(extensionDevelopmentLocationURI, false, ExtensionType.User, true, scanOptions.language, false /* do not validate */, undefined, scanOptions.productVersion ?? this.getProductVersion()); + const input = await this.createExtensionScannerInput(extensionDevelopmentLocationURI, false, ExtensionType.User, scanOptions.language, false /* do not validate */, undefined, scanOptions.productVersion ?? this.getProductVersion()); const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(input); return extensions.map(extension => { // Override the extension type from the existing extensions @@ -237,7 +233,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } async scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise { - const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, true, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion()); + const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion()); const extension = await this.extensionsScanner.scanExtension(extensionsScannerInput); if (!extension) { return null; @@ -249,7 +245,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } async scanOneOrMultipleExtensions(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise { - const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, true, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion()); + const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion()); const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(extensionsScannerInput); return this.applyScanOptions(extensions, extensionType, scanOptions, true); } @@ -405,7 +401,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem private async scanDefaultSystemExtensions(useCache: boolean, language: string | undefined): Promise { this.logService.trace('Started scanning system extensions'); - const extensionsScannerInput = await this.createExtensionScannerInput(this.systemExtensionsLocation, false, ExtensionType.System, true, language, true, undefined, this.getProductVersion()); + const extensionsScannerInput = await this.createExtensionScannerInput(this.systemExtensionsLocation, false, ExtensionType.System, language, true, undefined, this.getProductVersion()); const extensionsScanner = useCache && !extensionsScannerInput.devMode ? this.systemExtensionsCachedScanner : this.extensionsScanner; const result = await extensionsScanner.scanExtensions(extensionsScannerInput); this.logService.trace('Scanned system extensions:', result.length); @@ -435,7 +431,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem break; } } - const result = await Promise.all(devSystemExtensionsLocations.map(async location => this.extensionsScanner.scanExtension((await this.createExtensionScannerInput(location, false, ExtensionType.System, true, language, true, undefined, this.getProductVersion()))))); + const result = await Promise.all(devSystemExtensionsLocations.map(async location => this.extensionsScanner.scanExtension((await this.createExtensionScannerInput(location, false, ExtensionType.System, language, true, undefined, this.getProductVersion()))))); this.logService.trace('Scanned dev system extensions:', result.length); return coalesce(result); } @@ -449,7 +445,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } } - private async createExtensionScannerInput(location: URI, profile: boolean, type: ExtensionType, excludeObsolete: boolean, language: string | undefined, validate: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined, productVersion: IProductVersion): Promise { + private async createExtensionScannerInput(location: URI, profile: boolean, type: ExtensionType, language: string | undefined, validate: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined, productVersion: IProductVersion): Promise { const translations = await this.getTranslations(language ?? platform.language); const mtime = await this.getMtime(location); const applicationExtensionsLocation = profile && !this.uriIdentityService.extUri.isEqual(location, this.userDataProfilesService.defaultProfile.extensionsResource) ? this.userDataProfilesService.defaultProfile.extensionsResource : undefined; @@ -462,7 +458,6 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem profile, profileScanOptions, type, - excludeObsolete, validate, productVersion.version, productVersion.date, @@ -504,7 +499,6 @@ export class ExtensionScannerInput { public readonly profile: boolean, public readonly profileScanOptions: IProfileExtensionsScanOptions | undefined, public readonly type: ExtensionType, - public readonly excludeObsolete: boolean, public readonly validate: boolean, public readonly productVersion: string, public readonly productDate: string | undefined, @@ -534,7 +528,6 @@ export class ExtensionScannerInput { && a.profile === b.profile && objects.equals(a.profileScanOptions, b.profileScanOptions) && a.type === b.type - && a.excludeObsolete === b.excludeObsolete && a.validate === b.validate && a.productVersion === b.productVersion && a.productDate === b.productDate @@ -558,7 +551,6 @@ class ExtensionsScanner extends Disposable { private readonly extensionsEnabledWithApiProposalVersion: string[]; constructor( - private readonly obsoleteFile: URI, @IExtensionsProfileScannerService protected readonly extensionsProfileScannerService: IExtensionsProfileScannerService, @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService, @IFileService protected readonly fileService: IFileService, @@ -571,15 +563,9 @@ class ExtensionsScanner extends Disposable { } async scanExtensions(input: ExtensionScannerInput): Promise { - const extensions = input.profile ? await this.scanExtensionsFromProfile(input) : await this.scanExtensionsFromLocation(input); - let obsolete: IStringDictionary = {}; - if (input.excludeObsolete && input.type === ExtensionType.User) { - try { - const raw = (await this.fileService.readFile(this.obsoleteFile)).value.toString(); - obsolete = JSON.parse(raw); - } catch (error) { /* ignore */ } - } - return isEmptyObject(obsolete) ? extensions : extensions.filter(e => !obsolete[ExtensionKey.create(e).toString()]); + return input.profile + ? this.scanExtensionsFromProfile(input) + : this.scanExtensionsFromLocation(input); } private async scanExtensionsFromLocation(input: ExtensionScannerInput): Promise { @@ -596,7 +582,7 @@ class ExtensionsScanner extends Disposable { if (input.type === ExtensionType.User && basename(c.resource).indexOf('.') === 0) { return null; } - const extensionScannerInput = new ExtensionScannerInput(c.resource, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.excludeObsolete, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations); + const extensionScannerInput = new ExtensionScannerInput(c.resource, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations); return this.scanExtension(extensionScannerInput); })); return coalesce(extensions) @@ -622,7 +608,7 @@ class ExtensionsScanner extends Disposable { const extensions = await Promise.all( scannedProfileExtensions.map(async extensionInfo => { if (filter(extensionInfo)) { - const extensionScannerInput = new ExtensionScannerInput(extensionInfo.location, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.excludeObsolete, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations); + const extensionScannerInput = new ExtensionScannerInput(extensionInfo.location, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations); return this.scanExtension(extensionScannerInput, extensionInfo.metadata); } return null; @@ -891,7 +877,6 @@ class CachedExtensionsScanner extends ExtensionsScanner { constructor( private readonly currentProfile: IUserDataProfile, - obsoleteFile: URI, @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @IExtensionsProfileScannerService extensionsProfileScannerService: IExtensionsProfileScannerService, @IUriIdentityService uriIdentityService: IUriIdentityService, @@ -900,7 +885,7 @@ class CachedExtensionsScanner extends ExtensionsScanner { @IEnvironmentService environmentService: IEnvironmentService, @ILogService logService: ILogService ) { - super(obsoleteFile, extensionsProfileScannerService, uriIdentityService, fileService, productService, environmentService, logService); + super(extensionsProfileScannerService, uriIdentityService, fileService, productService, environmentService, logService); } override async scanExtensions(input: ExtensionScannerInput): Promise { diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 92405eefb753c..015c3dff393a0 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -60,7 +60,7 @@ export interface INativeServerExtensionManagementService extends IExtensionManag readonly _serviceBrand: undefined; scanAllUserInstalledExtensions(): Promise; scanInstalledExtensionAtLocation(location: URI): Promise; - markAsUninstalled(...extensions: IExtension[]): Promise; + deleteExtensions(...extensions: IExtension[]): Promise; } type ExtractExtensionResult = { readonly local: ILocalExtension; readonly verificationStatus?: ExtensionSignatureVerificationCode }; @@ -222,8 +222,8 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return this.extensionsScanner.copyExtensions(fromProfileLocation, toProfileLocation, { version: this.productService.version, date: this.productService.date }); } - markAsUninstalled(...extensions: IExtension[]): Promise { - return this.extensionsScanner.setUninstalled(...extensions); + deleteExtensions(...extensions: IExtension[]): Promise { + return this.extensionsScanner.setExtensionsForRemoval(...extensions); } async cleanUp(): Promise { @@ -480,8 +480,20 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi continue; } - // Check if this is a directory - if (!(await this.fileService.stat(resource)).isDirectory) { + // Ignore changes to the deleted folder + if (this.uriIdentityService.extUri.basename(resource).endsWith(DELETED_FOLDER_POSTFIX)) { + continue; + } + + try { + // Check if this is a directory + if (!(await this.fileService.stat(resource)).isDirectory) { + continue; + } + } catch (error) { + if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { + this.logService.error(error); + } continue; } @@ -502,23 +514,10 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi private async addExtensionsToProfile(extensions: [ILocalExtension, Metadata | undefined][], profileLocation: URI): Promise { const localExtensions = extensions.map(e => e[0]); - await this.setInstalled(localExtensions); + await this.extensionsScanner.unsetExtensionsForRemoval(...localExtensions.map(extension => ExtensionKey.create(extension))); 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(); - 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); - await this.extensionsScanner.setInstalled(extensionKey); - this.logService.info('Removed the extension from uninstalled list:', extensionKey.id); - } - } } type UpdateMetadataErrorClassification = { @@ -536,8 +535,8 @@ type UpdateMetadataErrorEvent = { export class ExtensionsScanner extends Disposable { - private readonly uninstalledResource: URI; - private readonly uninstalledFileLimiter: Queue; + private readonly obsoletedResource: URI; + private readonly obsoleteFileLimiter: Queue; private readonly _onExtract = this._register(new Emitter()); readonly onExtract = this._onExtract.event; @@ -555,13 +554,13 @@ export class ExtensionsScanner extends Disposable { @ILogService private readonly logService: ILogService, ) { super(); - this.uninstalledResource = joinPath(this.extensionsScannerService.userExtensionsLocation, '.obsolete'); - this.uninstalledFileLimiter = new Queue(); + this.obsoletedResource = joinPath(this.extensionsScannerService.userExtensionsLocation, '.obsolete'); + this.obsoleteFileLimiter = new Queue(); } async cleanUp(): Promise { await this.removeTemporarilyDeletedFolders(); - await this.removeUninstalledExtensions(); + await this.deleteExtensionsMarkedForRemoval(); await this.initializeMetadata(); } @@ -720,40 +719,38 @@ export class ExtensionsScanner extends Disposable { return this.scanLocalExtension(local.location, local.type, profileLocation); } - async getUninstalledExtensions(): Promise> { - try { - return await this.withUninstalledExtensions(); - } catch (error) { - throw toExtensionManagementError(error, ExtensionManagementErrorCode.ReadUninstalled); - } - } - - async setUninstalled(...extensions: IExtension[]): Promise { + async setExtensionsForRemoval(...extensions: IExtension[]): Promise { const extensionKeys: ExtensionKey[] = extensions.map(e => ExtensionKey.create(e)); - await this.withUninstalledExtensions(uninstalled => + await this.withRemovedExtensions(removedExtensions => extensionKeys.forEach(extensionKey => { - uninstalled[extensionKey.toString()] = true; - this.logService.info('Marked extension as uninstalled', extensionKey.toString()); + removedExtensions[extensionKey.toString()] = true; + this.logService.info('Marked extension as removed', extensionKey.toString()); })); } - async setInstalled(extensionKey: ExtensionKey): Promise { + async unsetExtensionsForRemoval(...extensionKeys: ExtensionKey[]): Promise { try { - await this.withUninstalledExtensions(uninstalled => delete uninstalled[extensionKey.toString()]); + const results: boolean[] = []; + await this.withRemovedExtensions(removedExtensions => + extensionKeys.forEach(extensionKey => { + if (removedExtensions[extensionKey.toString()]) { + results.push(true); + delete removedExtensions[extensionKey.toString()]; + } else { + results.push(false); + } + })); + return results; } catch (error) { - throw toExtensionManagementError(error, ExtensionManagementErrorCode.UnsetUninstalled); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.UnsetRemoved); } } - async removeExtension(extension: ILocalExtension | IScannedExtension, type: string): Promise { + async deleteExtension(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()]); + await this.unsetExtensionsForRemoval(ExtensionKey.create(extension)); } async copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial): Promise { @@ -792,11 +789,11 @@ export class ExtensionsScanner extends Disposable { this.logService.info(`Deleted ${type} extension from disk`, id, location.fsPath); } - private withUninstalledExtensions(updateFn?: (uninstalled: IStringDictionary) => void): Promise> { - return this.uninstalledFileLimiter.queue(async () => { + private withRemovedExtensions(updateFn?: (removed: IStringDictionary) => void): Promise> { + return this.obsoleteFileLimiter.queue(async () => { let raw: string | undefined; try { - const content = await this.fileService.readFile(this.uninstalledResource, 'utf8'); + const content = await this.fileService.readFile(this.obsoletedResource, 'utf8'); raw = content.value.toString(); } catch (error) { if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { @@ -804,23 +801,23 @@ export class ExtensionsScanner extends Disposable { } } - let uninstalled = {}; + let removed = {}; if (raw) { try { - uninstalled = JSON.parse(raw); + removed = JSON.parse(raw); } catch (e) { /* ignore */ } } if (updateFn) { - updateFn(uninstalled); - if (Object.keys(uninstalled).length) { - await this.fileService.writeFile(this.uninstalledResource, VSBuffer.fromString(JSON.stringify(uninstalled))); + updateFn(removed); + if (Object.keys(removed).length) { + await this.fileService.writeFile(this.obsoletedResource, VSBuffer.fromString(JSON.stringify(removed))); } else { - await this.fileService.del(this.uninstalledResource); + await this.fileService.del(this.obsoletedResource); } } - return uninstalled; + return removed; }); } @@ -898,19 +895,25 @@ export class ExtensionsScanner extends Disposable { })); } - private async removeUninstalledExtensions(): Promise { - const uninstalled = await this.getUninstalledExtensions(); - if (Object.keys(uninstalled).length === 0) { - this.logService.debug(`No uninstalled extensions found.`); + private async deleteExtensionsMarkedForRemoval(): Promise { + let removed: IStringDictionary; + try { + removed = await this.withRemovedExtensions(); + } catch (error) { + throw toExtensionManagementError(error, ExtensionManagementErrorCode.ReadRemoved); + } + + if (Object.keys(removed).length === 0) { + this.logService.debug(`No extensions are marked as removed.`); return; } - this.logService.debug(`Removing uninstalled extensions:`, Object.keys(uninstalled)); + this.logService.debug(`Deleting extensions marked as removed:`, Object.keys(removed)); - const extensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: true, includeUninstalled: true, includeInvalid: true }); // All user extensions + const extensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: true, includeInvalid: true }); // All user extensions const installed: Set = new Set(); for (const e of extensions) { - if (!uninstalled[ExtensionKey.create(e).toString()]) { + if (!removed[ExtensionKey.create(e).toString()]) { installed.add(e.identifier.id.toLowerCase()); } } @@ -928,8 +931,8 @@ export class ExtensionsScanner extends Disposable { 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 */ && removed[ExtensionKey.create(e).toString()]); + await Promise.allSettled(toRemove.map(e => this.deleteExtension(e, 'marked for removal'))); } private async removeTemporarilyDeletedFolders(): Promise { @@ -1021,7 +1024,7 @@ class InstallExtensionInProfileTask extends AbstractExtensionTask { - const uninstalled = await this.extensionsScanner.getUninstalledExtensions(); - if (!uninstalled[extensionKey.toString()]) { - return undefined; + private async unsetIfRemoved(extensionKey: ExtensionKey): Promise { + // If the same version of extension is marked as removed, remove it from there and return the local. + const [removed] = await this.extensionsScanner.unsetExtensionsForRemoval(extensionKey); + if (removed) { + this.logService.info('Removed the extension from removed list:', extensionKey.id); + const userExtensions = await this.extensionsScanner.scanAllUserExtensions(true); + return userExtensions.find(i => ExtensionKey.create(i).equals(extensionKey)); } - - 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)); + return undefined; } private async updateMetadata(extension: ILocalExtension, token: CancellationToken): Promise { @@ -1149,8 +1148,8 @@ class UninstallExtensionInProfileTask extends AbstractExtensionTask implem super(); } - protected async doRun(token: CancellationToken): Promise { - await this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension, this.options.profileLocation); + protected doRun(token: CancellationToken): Promise { + return this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension, this.options.profileLocation); } } diff --git a/src/vs/platform/extensionManagement/node/extensionsWatcher.ts b/src/vs/platform/extensionManagement/node/extensionsWatcher.ts index d2b65eaea553c..2c4e976a5a648 100644 --- a/src/vs/platform/extensionManagement/node/extensionsWatcher.ts +++ b/src/vs/platform/extensionManagement/node/extensionsWatcher.ts @@ -48,7 +48,7 @@ export class ExtensionsWatcher extends Disposable { await this.extensionsScannerService.initializeDefaultProfileExtensions(); await this.onDidChangeProfiles(this.userDataProfilesService.profiles); this.registerListeners(); - await this.uninstallExtensionsNotInProfiles(); + await this.deleteExtensionsNotInProfiles(); } private registerListeners(): void { @@ -102,7 +102,7 @@ export class ExtensionsWatcher extends Disposable { } private async onDidRemoveExtensions(e: DidRemoveProfileExtensionsEvent): Promise { - const extensionsToUninstall: IExtension[] = []; + const extensionsToDelete: IExtension[] = []; const promises: Promise[] = []; for (const extension of e.extensions) { const key = this.getKey(extension.identifier, extension.version); @@ -115,7 +115,7 @@ export class ExtensionsWatcher extends Disposable { promises.push(this.extensionManagementService.scanInstalledExtensionAtLocation(extension.location) .then(result => { if (result) { - extensionsToUninstall.push(result); + extensionsToDelete.push(result); } else { this.logService.info('Extension not found at the location', extension.location.toString()); } @@ -125,8 +125,8 @@ export class ExtensionsWatcher extends Disposable { } try { await Promise.all(promises); - if (extensionsToUninstall.length) { - await this.uninstallExtensionsNotInProfiles(extensionsToUninstall); + if (extensionsToDelete.length) { + await this.deleteExtensionsNotInProfiles(extensionsToDelete); } } catch (error) { this.logService.error(error); @@ -180,13 +180,13 @@ export class ExtensionsWatcher extends Disposable { } } - private async uninstallExtensionsNotInProfiles(toUninstall?: IExtension[]): Promise { - if (!toUninstall) { + private async deleteExtensionsNotInProfiles(toDelete?: IExtension[]): Promise { + if (!toDelete) { const installed = await this.extensionManagementService.scanAllUserInstalledExtensions(); - toUninstall = installed.filter(installedExtension => !this.allExtensions.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version))); + toDelete = installed.filter(installedExtension => !this.allExtensions.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version))); } - if (toUninstall.length) { - await this.extensionManagementService.markAsUninstalled(...toUninstall); + if (toDelete.length) { + await this.extensionManagementService.deleteExtensions(...toDelete); } } diff --git a/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts index 74d3ffcd738f8..551ba576d4459 100644 --- a/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts +++ b/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts @@ -224,31 +224,6 @@ suite('NativeExtensionsScanerService Test', () => { assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' }); }); - test('scan exclude uninstalled extensions', async () => { - await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' })); - await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub' })); - await instantiationService.get(IFileService).writeFile(joinPath(URI.file(instantiationService.get(INativeEnvironmentService).extensionsPath), '.obsolete'), VSBuffer.fromString(JSON.stringify({ 'pub.name2-1.0.0': true }))); - const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); - - const actual = await testObject.scanUserExtensions({}); - - assert.deepStrictEqual(actual.length, 1); - assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' }); - }); - - test('scan include uninstalled extensions', async () => { - await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' })); - await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub' })); - await instantiationService.get(IFileService).writeFile(joinPath(URI.file(instantiationService.get(INativeEnvironmentService).extensionsPath), '.obsolete'), VSBuffer.fromString(JSON.stringify({ 'pub.name2-1.0.0': true }))); - const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); - - const actual = await testObject.scanUserExtensions({ includeUninstalled: true }); - - assert.deepStrictEqual(actual.length, 2); - assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' }); - assert.deepStrictEqual(actual[1].identifier, { id: 'pub.name2' }); - }); - test('scan include invalid extensions', async () => { await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' })); await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub', engines: { vscode: '^1.67.0' } })); @@ -351,7 +326,7 @@ suite('ExtensionScannerInput', () => { ensureNoDisposablesAreLeakedInTestSuite(); test('compare inputs - location', () => { - const anInput = (location: URI, mtime: number | undefined) => new ExtensionScannerInput(location, mtime, undefined, undefined, false, undefined, ExtensionType.User, true, true, '1.1.1', undefined, undefined, true, undefined, {}); + const anInput = (location: URI, mtime: number | undefined) => new ExtensionScannerInput(location, mtime, undefined, undefined, false, undefined, ExtensionType.User, true, '1.1.1', undefined, undefined, true, undefined, {}); assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, undefined), anInput(ROOT, undefined)), true); assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, 100), anInput(ROOT, 100)), true); @@ -361,7 +336,7 @@ suite('ExtensionScannerInput', () => { }); test('compare inputs - application location', () => { - const anInput = (location: URI, mtime: number | undefined) => new ExtensionScannerInput(ROOT, undefined, location, mtime, false, undefined, ExtensionType.User, true, true, '1.1.1', undefined, undefined, true, undefined, {}); + const anInput = (location: URI, mtime: number | undefined) => new ExtensionScannerInput(ROOT, undefined, location, mtime, false, undefined, ExtensionType.User, true, '1.1.1', undefined, undefined, true, undefined, {}); assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, undefined), anInput(ROOT, undefined)), true); assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, 100), anInput(ROOT, 100)), true); @@ -371,7 +346,7 @@ suite('ExtensionScannerInput', () => { }); test('compare inputs - profile', () => { - const anInput = (profile: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined) => new ExtensionScannerInput(ROOT, undefined, undefined, undefined, profile, profileScanOptions, ExtensionType.User, true, true, '1.1.1', undefined, undefined, true, undefined, {}); + const anInput = (profile: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined) => new ExtensionScannerInput(ROOT, undefined, undefined, undefined, profile, profileScanOptions, ExtensionType.User, true, '1.1.1', undefined, undefined, true, undefined, {}); assert.strictEqual(ExtensionScannerInput.equals(anInput(true, { bailOutWhenFileNotFound: true }), anInput(true, { bailOutWhenFileNotFound: true })), true); assert.strictEqual(ExtensionScannerInput.equals(anInput(false, { bailOutWhenFileNotFound: true }), anInput(false, { bailOutWhenFileNotFound: true })), true); @@ -384,7 +359,7 @@ suite('ExtensionScannerInput', () => { }); test('compare inputs - extension type', () => { - const anInput = (type: ExtensionType) => new ExtensionScannerInput(ROOT, undefined, undefined, undefined, false, undefined, type, true, true, '1.1.1', undefined, undefined, true, undefined, {}); + const anInput = (type: ExtensionType) => new ExtensionScannerInput(ROOT, undefined, undefined, undefined, false, undefined, type, true, '1.1.1', undefined, undefined, true, undefined, {}); assert.strictEqual(ExtensionScannerInput.equals(anInput(ExtensionType.System), anInput(ExtensionType.System)), true); assert.strictEqual(ExtensionScannerInput.equals(anInput(ExtensionType.User), anInput(ExtensionType.User)), true); From d3b582676fa88ec3c4bdfd350aafebf851fcda28 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 17 Dec 2024 12:57:14 -0600 Subject: [PATCH 051/200] add rerun action to terminal chat to align w editor (#236381) --- .../chat/browser/terminalChat.ts | 1 + .../chat/browser/terminalChatActions.ts | 45 +++++++++++++++++++ .../chat/browser/terminalChatWidget.ts | 2 + 3 files changed, 48 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index b182074753e8b..5f16923d086b7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -17,6 +17,7 @@ export const enum TerminalChatCommandId { InsertCommand = 'workbench.action.terminal.chat.insertCommand', InsertFirstCommand = 'workbench.action.terminal.chat.insertFirstCommand', ViewInChat = 'workbench.action.terminal.chat.viewInChat', + RerunRequest = 'workbench.action.terminal.chat.rerunRequest', } export const MENU_TERMINAL_CHAT_WIDGET_INPUT_SIDE_TOOLBAR = MenuId.for('terminalChatWidget'); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 38bb507fe3b63..deeb48f48c792 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -8,6 +8,9 @@ import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; import { localize2 } from '../../../../../nls.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { IChatWidgetService } from '../../../chat/browser/chat.js'; +import { ChatAgentLocation } from '../../../chat/common/chatAgents.js'; +import { IChatService } from '../../../chat/common/chatService.js'; import { AbstractInlineChatAction } from '../../../inlineChat/browser/inlineChatActions.js'; import { isDetachedTerminalInstance } from '../../../terminal/browser/terminal.js'; import { registerActiveXtermAction } from '../../../terminal/browser/terminalActions.js'; @@ -209,6 +212,48 @@ registerActiveXtermAction({ } }); +registerActiveXtermAction({ + id: TerminalChatCommandId.RerunRequest, + title: localize2('chat.rerun.label', "Rerun Request"), + f1: false, + icon: Codicon.refresh, + category: AbstractInlineChatAction.category, + precondition: ContextKeyExpr.and( + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + TerminalChatContextKeys.requestActive.negate(), + ), + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KeyR + }, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_STATUS, + group: '0_main', + order: 5, + when: ContextKeyExpr.and(TerminalChatContextKeys.inputHasText.toNegated(), TerminalChatContextKeys.requestActive.negate()) + }, + run: async (_xterm, _accessor, activeInstance) => { + const chatService = _accessor.get(IChatService); + const chatWidgetService = _accessor.get(IChatWidgetService); + const contr = TerminalChatController.activeChatController; + const model = contr?.terminalChatWidget?.inlineChatWidget.chatWidget.viewModel?.model; + if (!model) { + return; + } + + const lastRequest = model.getRequests().at(-1); + if (lastRequest) { + const widget = chatWidgetService.getWidgetBySessionId(model.sessionId); + await chatService.resendRequest(lastRequest, { + noCommandDetection: false, + attempt: lastRequest.attempt + 1, + location: ChatAgentLocation.Terminal, + userSelectedModelId: widget?.input.currentLanguageModel + }); + } + } +}); + registerActiveXtermAction({ id: TerminalChatCommandId.ViewInChat, title: localize2('viewInChat', 'View in Chat'), diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index b668b55abc5dc..d1a310aae2914 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -127,6 +127,8 @@ export class TerminalChatWidget extends Disposable { menu: MENU_TERMINAL_CHAT_WIDGET_STATUS, options: { buttonConfigProvider: action => ({ + showLabel: action.id !== TerminalChatCommandId.RerunRequest, + showIcon: action.id === TerminalChatCommandId.RerunRequest, isSecondary: action.id !== TerminalChatCommandId.RunCommand && action.id !== TerminalChatCommandId.RunFirstCommand }) } From d08f30ded43d3fef500c50a22d2476e896e6d447 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 17 Dec 2024 10:57:39 -0800 Subject: [PATCH 052/200] Update area labels (#236116) --- .vscode/notebooks/grooming.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/grooming.github-issues b/.vscode/notebooks/grooming.github-issues index 4a4955dd7678d..e408bbd74215a 100644 --- a/.vscode/notebooks/grooming.github-issues +++ b/.vscode/notebooks/grooming.github-issues @@ -27,7 +27,7 @@ { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode assignee:$assignee is:open type:issue -label:\"info-needed\" -label:api -label:api-finalization -label:api-proposal -label:authentication -label:bisect-ext -label:bracket-pair-colorization -label:bracket-pair-guides -label:breadcrumbs -label:callhierarchy -label:chrome-devtools -label:cloud-changes -label:code-lens -label:command-center -label:comments -label:config -label:containers -label:context-keys -label:continue-working-on -label:css-less-scss -label:custom-editors -label:debug -label:debug-disassembly -label:dialogs -label:diff-editor -label:dropdown -label:editor-api -label:editor-autoclosing -label:editor-autoindent -label:editor-bracket-matching -label:editor-clipboard -label:editor-code-actions -label:editor-color-picker -label:editor-columnselect -label:editor-commands -label:editor-comments -label:editor-contrib -label:editor-core -label:editor-drag-and-drop -label:editor-error-widget -label:editor-find -label:editor-folding -label:editor-highlight -label:editor-hover -label:editor-indent-detection -label:editor-indent-guides -label:editor-input -label:editor-input-IME -label:editor-insets -label:editor-minimap -label:editor-multicursor -label:editor-parameter-hints -label:editor-render-whitespace -label:editor-rendering -label:editor-widgets -label:editor-RTL -label:editor-scrollbar -label:editor-sorting -label:editor-sticky-scroll -label:editor-symbols -label:editor-synced-region -label:editor-textbuffer -label:editor-theming -label:editor-wordnav -label:editor-wrapping -label:emmet -label:emmet-parse -label:error-list -label:extension-activation -label:extension-host -label:extension-prerelease -label:extension-recommendations -label:extensions -label:extensions-development -label:file-decorations -label:file-encoding -label:file-explorer -label:file-glob -label:file-io -label:file-nesting -label:file-watcher -label:font-rendering -label:formatting -label:getting-started -label:ghost-text -label:git -label:github -label:github-repositories -label:gpu -label:grammar -label:grid-widget -label:html -label:icon-brand -label:icons-product -label:image-preview -label:inlay-hints -label:inline-completions -label:install-update -label:intellisense-config -label:interactive-playground -label:interactive-window -label:issue-bot -label:issue-reporter -label:javascript -label:json -label:keybindings -label:keybindings-editor -label:keyboard-layout -label:chat -label:l10n-platform -label:label-provider -label:languages-basic -label:languages-diagnostics -label:languages-guessing -label:layout -label:lcd-text-rendering -label:list-widget -label:live-preview -label:log -label:markdown -label:marketplace -label:menus -label:merge-conflict -label:merge-editor -label:merge-editor-workbench -label:monaco-editor -label:native-file-dialog -label:network -label:notebook -label:notebook-accessibility -label:notebook-api -label:notebook-builtin-renderers -label:notebook-cell-editor -label:notebook-celltoolbar -label:notebook-clipboard -label:notebook-commands -label:notebook-commenting -label:notebook-debugging -label:notebook-diff -label:notebook-dnd -label:notebook-execution -label:notebook-find -label:notebook-folding -label:notebook-getting-started -label:notebook-globaltoolbar -label:notebook-ipynb -label:notebook-kernel -label:notebook-kernel-picker -label:notebook-language -label:notebook-layout -label:notebook-markdown -label:notebook-minimap -label:notebook-multiselect -label:notebook-output -label:notebook-perf -label:notebook-remote -label:notebook-rendering -label:notebook-serialization -label:notebook-statusbar -label:notebook-toc-outline -label:notebook-undo-redo -label:notebook-variables -label:notebook-workbench-integration -label:notebook-workflow -label:notebook-sticky-scroll -label:notebook-format -label:notebook-code-actions -label:open-editors -label:opener -label:outline -label:output -label:packaging -label:perf -label:perf-bloat -label:perf-startup -label:php -label:portable-mode -label:proxy -label:quick-open -label:quick-pick -label:references-viewlet -label:release-notes -label:remote -label:remote-connection -label:remote-explorer -label:remote-tunnel -label:rename -label:runCommands -label:sandbox -label:sash-widget -label:scm -label:screencast-mode -label:search -label:search-api -label:search-editor -label:search-replace -label:semantic-tokens -label:server -label:settings-editor -label:settings-sync -label:settings-sync-server -label:shared-process -label:simple-file-dialog -label:smart-select -label:snap -label:snippets -label:splitview-widget -label:ssh -label:suggest -label:table-widget -label:tasks -label:telemetry -label:terminal -label:terminal-accessibility -label:terminal-conpty -label:terminal-editors -label:terminal-external -label:terminal-find -label:terminal-input -label:terminal-layout -label:terminal-links -label:terminal-local-echo -label:terminal-persistence -label:terminal-process -label:terminal-profiles -label:terminal-quick-fix -label:terminal-rendering -label:terminal-shell-bash -label:terminal-shell-cmd -label:terminal-shell-fish -label:terminal-shell-git-bash -label:terminal-shell-integration -label:terminal-shell-pwsh -label:terminal-shell-zsh -label:terminal-sticky-scroll -label:terminal-tabs -label:testing -label:themes -label:timeline -label:timeline-git -label:timeline-local-history -label:titlebar -label:tokenization -label:touch/pointer -label:trackpad/scroll -label:tree-views -label:tree-widget -label:typescript -label:undo-redo -label:unicode-highlight -label:uri -label:user-profiles -label:ux -label:variable-resolving -label:VIM -label:virtual-workspaces -label:vscode-website -label:vscode.dev -label:web -label:webview -label:webview-views -label:workbench-actions -label:workbench-banner -label:workbench-cli -label:workbench-diagnostics -label:workbench-dnd -label:workbench-editor-grid -label:workbench-editor-groups -label:workbench-editor-resolver -label:workbench-editors -label:workbench-electron -label:workbench-fonts -label:workbench-history -label:workbench-hot-exit -label:workbench-hover -label:workbench-launch -label:workbench-link -label:workbench-multiroot -label:workbench-notifications -label:workbench-os-integration -label:workbench-rapid-render -label:workbench-run-as-admin -label:workbench-state -label:workbench-status -label:workbench-tabs -label:workbench-touchbar -label:workbench-untitled-editors -label:workbench-views -label:workbench-welcome -label:workbench-window -label:workbench-workspace -label:workbench-zen -label:workspace-edit -label:workspace-symbols -label:workspace-trust -label:zoom -label:inline-chat -label:panel-chat -label:quick-chat -label:tasks -label:error-list -label:winget -label:tree-views -label:freeze-slow-crash-leak -label:engineering -label:cross-file-editing" + "value": "repo:microsoft/vscode assignee:$assignee is:open type:issue -label:\"info-needed\" -label:api -label:api-finalization -label:api-proposal -label:authentication -label:bisect-ext -label:bracket-pair-colorization -label:bracket-pair-guides -label:breadcrumbs -label:callhierarchy -label:chrome-devtools -label:cloud-changes -label:code-lens -label:command-center -label:comments -label:config -label:containers -label:context-keys -label:continue-working-on -label:css-less-scss -label:custom-editors -label:debug -label:debug-disassembly -label:dialogs -label:diff-editor -label:dropdown -label:editor-api -label:editor-autoclosing -label:editor-autoindent -label:editor-bracket-matching -label:editor-clipboard -label:editor-code-actions -label:editor-color-picker -label:editor-columnselect -label:editor-commands -label:editor-comments -label:editor-contrib -label:editor-core -label:editor-drag-and-drop -label:editor-error-widget -label:editor-find -label:editor-folding -label:editor-highlight -label:editor-hover -label:editor-indent-detection -label:editor-indent-guides -label:editor-input -label:editor-input-IME -label:editor-insets -label:editor-minimap -label:editor-multicursor -label:editor-parameter-hints -label:editor-render-whitespace -label:editor-rendering -label:editor-widgets -label:editor-RTL -label:editor-scrollbar -label:editor-sorting -label:editor-sticky-scroll -label:editor-symbols -label:editor-synced-region -label:editor-textbuffer -label:editor-theming -label:editor-wordnav -label:editor-wrapping -label:emmet -label:emmet-parse -label:error-list -label:extension-activation -label:extension-host -label:extension-prerelease -label:extension-recommendations -label:extensions -label:extensions-development -label:file-decorations -label:file-encoding -label:file-explorer -label:file-glob -label:file-io -label:file-nesting -label:file-watcher -label:font-rendering -label:formatting -label:getting-started -label:ghost-text -label:git -label:github -label:github-repositories -label:gpu -label:grammar -label:grid-widget -label:html -label:icon-brand -label:icons-product -label:image-preview -label:inlay-hints -label:inline-completions -label:install-update -label:intellisense-config -label:interactive-playground -label:interactive-window -label:issue-bot -label:issue-reporter -label:javascript -label:json -label:keybindings -label:keybindings-editor -label:keyboard-layout -label:chat -label:l10n-platform -label:label-provider -label:languages-basic -label:languages-diagnostics -label:languages-guessing -label:layout -label:lcd-text-rendering -label:list-widget -label:live-preview -label:log -label:markdown -label:marketplace -label:menus -label:merge-conflict -label:merge-editor -label:merge-editor-workbench -label:monaco-editor -label:native-file-dialog -label:network -label:notebook -label:notebook-accessibility -label:notebook-api -label:notebook-cell-editor -label:notebook-celltoolbar -label:notebook-clipboard -label:notebook-commands -label:notebook-debugging -label:notebook-diff -label:notebook-dnd -label:notebook-execution -label:notebook-find -label:notebook-folding -label:notebook-getting-started -label:notebook-globaltoolbar -label:notebook-ipynb -label:notebook-kernel -label:notebook-kernel-picker -label:notebook-language -label:notebook-layout -label:notebook-markdown -label:notebook-output -label:notebook-perf -label:notebook-remote -label:notebook-serialization -label:notebook-statusbar -label:notebook-toc-outline -label:notebook-undo-redo -label:notebook-variables -label:notebook-workbench-integration -label:notebook-workflow -label:notebook-sticky-scroll -label:notebook-format -label:notebook-code-actions -label:open-editors -label:opener -label:outline -label:output -label:packaging -label:perf -label:perf-bloat -label:perf-startup -label:php -label:portable-mode -label:proxy -label:quick-open -label:quick-pick -label:references-viewlet -label:release-notes -label:remote -label:remote-connection -label:remote-explorer -label:remote-tunnel -label:rename -label:runCommands -label:sandbox -label:sash-widget -label:scm -label:screencast-mode -label:search -label:search-api -label:search-editor -label:search-replace -label:semantic-tokens -label:server -label:settings-editor -label:settings-sync -label:settings-sync-server -label:shared-process -label:simple-file-dialog -label:smart-select -label:snap -label:snippets -label:splitview-widget -label:ssh -label:suggest -label:table-widget -label:tasks -label:telemetry -label:terminal -label:terminal-accessibility -label:terminal-conpty -label:terminal-editors -label:terminal-external -label:terminal-find -label:terminal-input -label:terminal-layout -label:terminal-links -label:terminal-local-echo -label:terminal-persistence -label:terminal-process -label:terminal-profiles -label:terminal-quick-fix -label:terminal-rendering -label:terminal-shell-bash -label:terminal-shell-cmd -label:terminal-shell-fish -label:terminal-shell-git-bash -label:terminal-shell-integration -label:terminal-shell-pwsh -label:terminal-shell-zsh -label:terminal-sticky-scroll -label:terminal-tabs -label:testing -label:themes -label:timeline -label:timeline-git -label:timeline-local-history -label:titlebar -label:tokenization -label:touch/pointer -label:trackpad/scroll -label:tree-views -label:tree-widget -label:typescript -label:undo-redo -label:unicode-highlight -label:uri -label:user-profiles -label:ux -label:variable-resolving -label:VIM -label:virtual-workspaces -label:vscode-website -label:vscode.dev -label:web -label:webview -label:webview-views -label:workbench-actions -label:workbench-banner -label:workbench-cli -label:workbench-diagnostics -label:workbench-dnd -label:workbench-editor-grid -label:workbench-editor-groups -label:workbench-editor-resolver -label:workbench-editors -label:workbench-electron -label:workbench-fonts -label:workbench-history -label:workbench-hot-exit -label:workbench-hover -label:workbench-launch -label:workbench-link -label:workbench-multiroot -label:workbench-notifications -label:workbench-os-integration -label:workbench-rapid-render -label:workbench-run-as-admin -label:workbench-state -label:workbench-status -label:workbench-tabs -label:workbench-touchbar -label:workbench-untitled-editors -label:workbench-views -label:workbench-welcome -label:workbench-window -label:workbench-workspace -label:workbench-zen -label:workspace-edit -label:workspace-symbols -label:workspace-trust -label:zoom -label:inline-chat -label:panel-chat -label:quick-chat -label:tasks -label:error-list -label:winget -label:tree-views -label:freeze-slow-crash-leak -label:engineering -label:cross-file-editing" }, { "kind": 1, From 2bb6383e85fdfc2c28bd479675b9b296ebc7f268 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Dec 2024 10:59:05 -0800 Subject: [PATCH 053/200] Pick up latest TS nightly --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index fd395ebd43207..60dc545ca3754 100644 --- a/package-lock.json +++ b/package-lock.json @@ -153,7 +153,7 @@ "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^5.8.0-dev.20241212", + "typescript": "^5.8.0-dev.20241217", "typescript-eslint": "^8.8.0", "util": "^0.12.4", "webpack": "^5.94.0", @@ -17599,9 +17599,9 @@ "dev": true }, "node_modules/typescript": { - "version": "5.8.0-dev.20241212", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.0-dev.20241212.tgz", - "integrity": "sha512-DL+rd76Ze4iHIFTT6+f8SNdxkTYnR0cy6e0QRljOfyr2s0TrO2L9pAOB1dJnSizTAjxou7lIRpUWwxVOIyiMWg==", + "version": "5.8.0-dev.20241217", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.0-dev.20241217.tgz", + "integrity": "sha512-Q/I+eHfiwN0aWhitenThTT2FcA1lTlUZR1z+6d2WaD/8/wHfdjQjdHynCpYXjAwDkfG8Apf9LdzZ3rLRD3O9iQ==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index eb0abcc3221b3..8dd5e0a86a37b 100644 --- a/package.json +++ b/package.json @@ -211,7 +211,7 @@ "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^5.8.0-dev.20241212", + "typescript": "^5.8.0-dev.20241217", "typescript-eslint": "^8.8.0", "util": "^0.12.4", "webpack": "^5.94.0", From 068676662c8067ea8f6a96ebcc9d9e9332e2b2ba Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 17 Dec 2024 20:05:05 +0100 Subject: [PATCH 054/200] Fixes https://github.com/microsoft/vscode-copilot/issues/10296 (#236383) --- .../controller/inlineCompletionsController.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index 6be0ecd1c90fb..f3720436c2f65 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -38,6 +38,8 @@ import { InlineCompletionContextKeys } from './inlineCompletionContextKeys.js'; import { InlineCompletionsView } from '../view/inlineCompletionsView.js'; export class InlineCompletionsController extends Disposable { + private static readonly _instances = new Set(); + public static hot = createHotClass(InlineCompletionsController); public static ID = 'editor.contrib.inlineCompletionsController'; @@ -116,6 +118,22 @@ export class InlineCompletionsController extends Disposable { ) { super(); + InlineCompletionsController._instances.add(this); + this._register(toDisposable(() => InlineCompletionsController._instances.delete(this))); + + this._register(autorun(reader => { + // Cancel all other inline completions when a new one starts + const model = this.model.read(reader); + if (!model) { return; } + if (model.state.read(reader) !== undefined) { + for (const ctrl of InlineCompletionsController._instances) { + if (ctrl !== this) { + ctrl.reject(); + } + } + } + })); + this._register(runOnChange(this._editorObs.onDidType, (_value, _changes) => { if (this._enabled.get()) { this.model.get()?.trigger(); From e689b912ba8d46d224fc80ca56c641772476a7da Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 17 Dec 2024 12:12:49 -0700 Subject: [PATCH 055/200] Update telemetry package (#236378) --- extensions/git/package-lock.json | 141 ++++++++++-------- extensions/git/package.json | 2 +- .../github-authentication/package-lock.json | 141 ++++++++++-------- extensions/github-authentication/package.json | 2 +- extensions/github/package-lock.json | 141 ++++++++++-------- extensions/github/package.json | 2 +- .../html-language-features/package-lock.json | 141 ++++++++++-------- .../html-language-features/package.json | 2 +- .../json-language-features/package-lock.json | 141 ++++++++++-------- .../json-language-features/package.json | 2 +- .../package-lock.json | 141 ++++++++++-------- .../markdown-language-features/package.json | 2 +- extensions/media-preview/package-lock.json | 141 ++++++++++-------- extensions/media-preview/package.json | 2 +- extensions/merge-conflict/package-lock.json | 141 ++++++++++-------- extensions/merge-conflict/package.json | 2 +- .../package-lock.json | 141 ++++++++++-------- .../microsoft-authentication/package.json | 2 +- extensions/simple-browser/package-lock.json | 141 ++++++++++-------- extensions/simple-browser/package.json | 2 +- .../package-lock.json | 141 ++++++++++-------- .../typescript-language-features/package.json | 2 +- 22 files changed, 847 insertions(+), 726 deletions(-) diff --git a/extensions/git/package-lock.json b/extensions/git/package-lock.json index cf05db079db06..bc150555c70cc 100644 --- a/extensions/git/package-lock.json +++ b/extensions/git/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@joaomoreno/unique-names-generator": "^5.2.0", - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "@vscode/iconv-lite-umd": "0.7.0", "byline": "^5.0.0", "file-type": "16.5.4", @@ -40,118 +40,128 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.0.3.tgz", - "integrity": "sha512-FrxNLVAPsAvD7+l63TlNS/Kodvpct2WulpDSn1dI4Xuy0kF4E2H867kHdwL/iY1Bj3zA3FSy/jvE4+OcDws7ug==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", + "integrity": "sha512-3gbDUQgAO8EoyQTNcAEkxpuPnioC0May13P1l1l0NKZ128L9Ts/sj8QsfwCRTjHz0HThlA+4FptcAJXNYUy3rg==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.0.3.tgz", - "integrity": "sha512-uewvmUtXKd7ttypiKQGdYI6i7UUpPkOznLayzIFrJ4r2xnG6jhPjpKRncHFXPQcM4XSWO3yf5PQ3xAbPq9t7ZQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.4.tgz", + "integrity": "sha512-nlKjWricDj0Tn68Dt0P8lX9a+X7LYrqJ6/iSfQwMfDhRIGLqW+wxx8gxS+iGWC/oc8zMQAeiZaemUpCwQcwpRQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "4.0.3", + "@microsoft/1ds-core-js": "4.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.4.tgz", - "integrity": "sha512-6TlfExmErQ8Y+/ChbkyWl+jyt4wg3T6p7lwXDsUCB0LgZmlEWMaCUS0YlT73JCWmE8j7vxW8yUm0lgsgmHns3A==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.4.tgz", + "integrity": "sha512-Z4nrxYwGKP9iyrYtm7iPQXVOFy4FsEsX0nDKkAi96Qpgw+vEh6NH4ORxMMuES0EollBQ3faJyvYCwckuCVIj0g==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.4.tgz", - "integrity": "sha512-r5gWaw/K9+tKfuo2GtDiDiKASgOkPOCrKW+wZzFvuR06uuwvWjbVQ6yW/YbnfuhRF5M65ksUiMi0eCMwEOGq7Q==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.4.tgz", + "integrity": "sha512-4ms16MlIvcP4WiUPqopifNxcWCcrXQJ2ADAK/75uok2mNQe6ZNRsqb/P+pvhUxc8A5HRlvoXPP1ptDSN5Girgw==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.4.tgz", - "integrity": "sha512-anxy5kEkqBmVoEqJiJzaaXXA0wzqZi9U4zGd05xFJ04lWckP8dG3zyT3+GGdg7rDelqLTNGxndeYoFmDv63u1g==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.4.tgz", + "integrity": "sha512-MummANF0mgKIkdvVvfmHQTBliK114IZLRhTL0X0Ep+zjDwWMHqYZgew0nlFKAl6ggu42abPZFK5afpE7qjtYJA==", + "license": "MIT", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-shims": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.0.4.tgz", - "integrity": "sha512-KfoxPlLlf0JT12ADb23C5iGye/yFouoMgHEKULxkSQcYY9SsW/8rVrqqvoYKAL+u215CZU2A8Kc8sR3ehEaPCQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.4.tgz", + "integrity": "sha512-OpEPXr8vU/t/M8T9jvWJzJx/pCyygIiR1nGM/2PTde0wn7anl71Gxl5fWol7K/WwFEORNjkL3CEyWOyDc+28AA==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-channel-js": "3.0.4", - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-channel-js": "3.3.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/dynamicproto-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", - "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", + "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } }, "node_modules/@nevware21/ts-async": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.3.0.tgz", - "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.4.tgz", + "integrity": "sha512-IBTyj29GwGlxfzXw2NPnzty+w0Adx61Eze1/lknH/XIVdxtF9UnOpk76tnrHXWa6j84a1RR9hsOcHQPFv9qJjA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.10.0 < 2.x" + "@nevware21/ts-utils": ">= 0.11.6 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.10.1.tgz", - "integrity": "sha512-pMny25NnF2/MJwdqC3Iyjm2pGIXNxni4AROpcqDeWa+td9JMUY4bUS9uU9XW+BoBRqTLUL+WURF9SOd/6OQzRg==" + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.6.tgz", + "integrity": "sha512-OUUJTh3fnaUSzg9DEHgv3d7jC+DnPL65mIO7RaR+jWve7+MmcgIvF79gY97DPQ4frH+IpNR78YAYd/dW4gK3kg==", + "license": "MIT" }, "node_modules/@tokenizer/token": { "version": "0.3.0", @@ -195,13 +205,14 @@ "dev": true }, "node_modules/@vscode/extension-telemetry": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.0.tgz", - "integrity": "sha512-37RxGHXrs3GoXPgCUKQhghEu0gxs8j27RLjQwwtSf4WhPdJKz8UrqMYzpsXlliQ05zURYmtdGZst9C6+hfWXaQ==", + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.8.tgz", + "integrity": "sha512-7YcKoUvmHlIB8QYCE4FNzt3ErHi9gQPhdCM3ZWtpw1bxPT0I+lMdx52KHlzTNoJzQ2NvMX7HyzyDwBEiMgTrWQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "^4.0.3", - "@microsoft/1ds-post-js": "^4.0.3", - "@microsoft/applicationinsights-web-basic": "^3.0.4" + "@microsoft/1ds-core-js": "^4.3.4", + "@microsoft/1ds-post-js": "^4.3.4", + "@microsoft/applicationinsights-web-basic": "^3.3.4" }, "engines": { "vscode": "^1.75.0" diff --git a/extensions/git/package.json b/extensions/git/package.json index 9aedfbb16e3a7..8b8f52643a896 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -3471,7 +3471,7 @@ }, "dependencies": { "@joaomoreno/unique-names-generator": "^5.2.0", - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "@vscode/iconv-lite-umd": "0.7.0", "byline": "^5.0.0", "file-type": "16.5.4", diff --git a/extensions/github-authentication/package-lock.json b/extensions/github-authentication/package-lock.json index c150fa7acc362..cbc9e16b75f30 100644 --- a/extensions/github-authentication/package-lock.json +++ b/extensions/github-authentication/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.2", "license": "MIT", "dependencies": { - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "node-fetch": "2.6.7", "vscode-tas-client": "^0.1.84" }, @@ -23,118 +23,128 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.0.3.tgz", - "integrity": "sha512-FrxNLVAPsAvD7+l63TlNS/Kodvpct2WulpDSn1dI4Xuy0kF4E2H867kHdwL/iY1Bj3zA3FSy/jvE4+OcDws7ug==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", + "integrity": "sha512-3gbDUQgAO8EoyQTNcAEkxpuPnioC0May13P1l1l0NKZ128L9Ts/sj8QsfwCRTjHz0HThlA+4FptcAJXNYUy3rg==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.0.3.tgz", - "integrity": "sha512-uewvmUtXKd7ttypiKQGdYI6i7UUpPkOznLayzIFrJ4r2xnG6jhPjpKRncHFXPQcM4XSWO3yf5PQ3xAbPq9t7ZQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.4.tgz", + "integrity": "sha512-nlKjWricDj0Tn68Dt0P8lX9a+X7LYrqJ6/iSfQwMfDhRIGLqW+wxx8gxS+iGWC/oc8zMQAeiZaemUpCwQcwpRQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "4.0.3", + "@microsoft/1ds-core-js": "4.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.4.tgz", - "integrity": "sha512-6TlfExmErQ8Y+/ChbkyWl+jyt4wg3T6p7lwXDsUCB0LgZmlEWMaCUS0YlT73JCWmE8j7vxW8yUm0lgsgmHns3A==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.4.tgz", + "integrity": "sha512-Z4nrxYwGKP9iyrYtm7iPQXVOFy4FsEsX0nDKkAi96Qpgw+vEh6NH4ORxMMuES0EollBQ3faJyvYCwckuCVIj0g==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.4.tgz", - "integrity": "sha512-r5gWaw/K9+tKfuo2GtDiDiKASgOkPOCrKW+wZzFvuR06uuwvWjbVQ6yW/YbnfuhRF5M65ksUiMi0eCMwEOGq7Q==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.4.tgz", + "integrity": "sha512-4ms16MlIvcP4WiUPqopifNxcWCcrXQJ2ADAK/75uok2mNQe6ZNRsqb/P+pvhUxc8A5HRlvoXPP1ptDSN5Girgw==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.4.tgz", - "integrity": "sha512-anxy5kEkqBmVoEqJiJzaaXXA0wzqZi9U4zGd05xFJ04lWckP8dG3zyT3+GGdg7rDelqLTNGxndeYoFmDv63u1g==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.4.tgz", + "integrity": "sha512-MummANF0mgKIkdvVvfmHQTBliK114IZLRhTL0X0Ep+zjDwWMHqYZgew0nlFKAl6ggu42abPZFK5afpE7qjtYJA==", + "license": "MIT", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-shims": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.0.4.tgz", - "integrity": "sha512-KfoxPlLlf0JT12ADb23C5iGye/yFouoMgHEKULxkSQcYY9SsW/8rVrqqvoYKAL+u215CZU2A8Kc8sR3ehEaPCQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.4.tgz", + "integrity": "sha512-OpEPXr8vU/t/M8T9jvWJzJx/pCyygIiR1nGM/2PTde0wn7anl71Gxl5fWol7K/WwFEORNjkL3CEyWOyDc+28AA==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-channel-js": "3.0.4", - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-channel-js": "3.3.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/dynamicproto-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", - "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", + "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } }, "node_modules/@nevware21/ts-async": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.3.0.tgz", - "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.4.tgz", + "integrity": "sha512-IBTyj29GwGlxfzXw2NPnzty+w0Adx61Eze1/lknH/XIVdxtF9UnOpk76tnrHXWa6j84a1RR9hsOcHQPFv9qJjA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.10.0 < 2.x" + "@nevware21/ts-utils": ">= 0.11.6 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.10.1.tgz", - "integrity": "sha512-pMny25NnF2/MJwdqC3Iyjm2pGIXNxni4AROpcqDeWa+td9JMUY4bUS9uU9XW+BoBRqTLUL+WURF9SOd/6OQzRg==" + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.6.tgz", + "integrity": "sha512-OUUJTh3fnaUSzg9DEHgv3d7jC+DnPL65mIO7RaR+jWve7+MmcgIvF79gY97DPQ4frH+IpNR78YAYd/dW4gK3kg==", + "license": "MIT" }, "node_modules/@types/mocha": { "version": "9.1.1", @@ -162,13 +172,14 @@ } }, "node_modules/@vscode/extension-telemetry": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.0.tgz", - "integrity": "sha512-37RxGHXrs3GoXPgCUKQhghEu0gxs8j27RLjQwwtSf4WhPdJKz8UrqMYzpsXlliQ05zURYmtdGZst9C6+hfWXaQ==", + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.8.tgz", + "integrity": "sha512-7YcKoUvmHlIB8QYCE4FNzt3ErHi9gQPhdCM3ZWtpw1bxPT0I+lMdx52KHlzTNoJzQ2NvMX7HyzyDwBEiMgTrWQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "^4.0.3", - "@microsoft/1ds-post-js": "^4.0.3", - "@microsoft/applicationinsights-web-basic": "^3.0.4" + "@microsoft/1ds-core-js": "^4.3.4", + "@microsoft/1ds-post-js": "^4.3.4", + "@microsoft/applicationinsights-web-basic": "^3.3.4" }, "engines": { "vscode": "^1.75.0" diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index cbc1ddc11dd53..80b5d2c920e81 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -61,7 +61,7 @@ }, "dependencies": { "node-fetch": "2.6.7", - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "vscode-tas-client": "^0.1.84" }, "devDependencies": { diff --git a/extensions/github/package-lock.json b/extensions/github/package-lock.json index 41de66d1a9ba8..1b7dc727a928f 100644 --- a/extensions/github/package-lock.json +++ b/extensions/github/package-lock.json @@ -12,7 +12,7 @@ "@octokit/graphql": "5.0.5", "@octokit/graphql-schema": "14.4.0", "@octokit/rest": "19.0.4", - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "tunnel": "^0.0.6" }, "devDependencies": { @@ -23,118 +23,128 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.0.3.tgz", - "integrity": "sha512-FrxNLVAPsAvD7+l63TlNS/Kodvpct2WulpDSn1dI4Xuy0kF4E2H867kHdwL/iY1Bj3zA3FSy/jvE4+OcDws7ug==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", + "integrity": "sha512-3gbDUQgAO8EoyQTNcAEkxpuPnioC0May13P1l1l0NKZ128L9Ts/sj8QsfwCRTjHz0HThlA+4FptcAJXNYUy3rg==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.0.3.tgz", - "integrity": "sha512-uewvmUtXKd7ttypiKQGdYI6i7UUpPkOznLayzIFrJ4r2xnG6jhPjpKRncHFXPQcM4XSWO3yf5PQ3xAbPq9t7ZQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.4.tgz", + "integrity": "sha512-nlKjWricDj0Tn68Dt0P8lX9a+X7LYrqJ6/iSfQwMfDhRIGLqW+wxx8gxS+iGWC/oc8zMQAeiZaemUpCwQcwpRQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "4.0.3", + "@microsoft/1ds-core-js": "4.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.4.tgz", - "integrity": "sha512-6TlfExmErQ8Y+/ChbkyWl+jyt4wg3T6p7lwXDsUCB0LgZmlEWMaCUS0YlT73JCWmE8j7vxW8yUm0lgsgmHns3A==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.4.tgz", + "integrity": "sha512-Z4nrxYwGKP9iyrYtm7iPQXVOFy4FsEsX0nDKkAi96Qpgw+vEh6NH4ORxMMuES0EollBQ3faJyvYCwckuCVIj0g==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.4.tgz", - "integrity": "sha512-r5gWaw/K9+tKfuo2GtDiDiKASgOkPOCrKW+wZzFvuR06uuwvWjbVQ6yW/YbnfuhRF5M65ksUiMi0eCMwEOGq7Q==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.4.tgz", + "integrity": "sha512-4ms16MlIvcP4WiUPqopifNxcWCcrXQJ2ADAK/75uok2mNQe6ZNRsqb/P+pvhUxc8A5HRlvoXPP1ptDSN5Girgw==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.4.tgz", - "integrity": "sha512-anxy5kEkqBmVoEqJiJzaaXXA0wzqZi9U4zGd05xFJ04lWckP8dG3zyT3+GGdg7rDelqLTNGxndeYoFmDv63u1g==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.4.tgz", + "integrity": "sha512-MummANF0mgKIkdvVvfmHQTBliK114IZLRhTL0X0Ep+zjDwWMHqYZgew0nlFKAl6ggu42abPZFK5afpE7qjtYJA==", + "license": "MIT", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-shims": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.0.4.tgz", - "integrity": "sha512-KfoxPlLlf0JT12ADb23C5iGye/yFouoMgHEKULxkSQcYY9SsW/8rVrqqvoYKAL+u215CZU2A8Kc8sR3ehEaPCQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.4.tgz", + "integrity": "sha512-OpEPXr8vU/t/M8T9jvWJzJx/pCyygIiR1nGM/2PTde0wn7anl71Gxl5fWol7K/WwFEORNjkL3CEyWOyDc+28AA==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-channel-js": "3.0.4", - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-channel-js": "3.3.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/dynamicproto-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", - "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", + "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } }, "node_modules/@nevware21/ts-async": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.3.0.tgz", - "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.4.tgz", + "integrity": "sha512-IBTyj29GwGlxfzXw2NPnzty+w0Adx61Eze1/lknH/XIVdxtF9UnOpk76tnrHXWa6j84a1RR9hsOcHQPFv9qJjA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.10.0 < 2.x" + "@nevware21/ts-utils": ">= 0.11.6 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.10.1.tgz", - "integrity": "sha512-pMny25NnF2/MJwdqC3Iyjm2pGIXNxni4AROpcqDeWa+td9JMUY4bUS9uU9XW+BoBRqTLUL+WURF9SOd/6OQzRg==" + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.6.tgz", + "integrity": "sha512-OUUJTh3fnaUSzg9DEHgv3d7jC+DnPL65mIO7RaR+jWve7+MmcgIvF79gY97DPQ4frH+IpNR78YAYd/dW4gK3kg==", + "license": "MIT" }, "node_modules/@octokit/auth-token": { "version": "3.0.1", @@ -393,13 +403,14 @@ } }, "node_modules/@vscode/extension-telemetry": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.0.tgz", - "integrity": "sha512-37RxGHXrs3GoXPgCUKQhghEu0gxs8j27RLjQwwtSf4WhPdJKz8UrqMYzpsXlliQ05zURYmtdGZst9C6+hfWXaQ==", + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.8.tgz", + "integrity": "sha512-7YcKoUvmHlIB8QYCE4FNzt3ErHi9gQPhdCM3ZWtpw1bxPT0I+lMdx52KHlzTNoJzQ2NvMX7HyzyDwBEiMgTrWQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "^4.0.3", - "@microsoft/1ds-post-js": "^4.0.3", - "@microsoft/applicationinsights-web-basic": "^3.0.4" + "@microsoft/1ds-core-js": "^4.3.4", + "@microsoft/1ds-post-js": "^4.3.4", + "@microsoft/applicationinsights-web-basic": "^3.3.4" }, "engines": { "vscode": "^1.75.0" diff --git a/extensions/github/package.json b/extensions/github/package.json index e84b35d19b760..f99a41d5979e6 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -183,7 +183,7 @@ "@octokit/graphql-schema": "14.4.0", "@octokit/rest": "19.0.4", "tunnel": "^0.0.6", - "@vscode/extension-telemetry": "^0.9.0" + "@vscode/extension-telemetry": "^0.9.8" }, "devDependencies": { "@types/node": "20.x" diff --git a/extensions/html-language-features/package-lock.json b/extensions/html-language-features/package-lock.json index fbb95e522dd28..20b145615338d 100644 --- a/extensions/html-language-features/package-lock.json +++ b/extensions/html-language-features/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "vscode-languageclient": "^10.0.0-next.13", "vscode-uri": "^3.0.8" }, @@ -21,118 +21,128 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.0.3.tgz", - "integrity": "sha512-FrxNLVAPsAvD7+l63TlNS/Kodvpct2WulpDSn1dI4Xuy0kF4E2H867kHdwL/iY1Bj3zA3FSy/jvE4+OcDws7ug==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", + "integrity": "sha512-3gbDUQgAO8EoyQTNcAEkxpuPnioC0May13P1l1l0NKZ128L9Ts/sj8QsfwCRTjHz0HThlA+4FptcAJXNYUy3rg==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.0.3.tgz", - "integrity": "sha512-uewvmUtXKd7ttypiKQGdYI6i7UUpPkOznLayzIFrJ4r2xnG6jhPjpKRncHFXPQcM4XSWO3yf5PQ3xAbPq9t7ZQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.4.tgz", + "integrity": "sha512-nlKjWricDj0Tn68Dt0P8lX9a+X7LYrqJ6/iSfQwMfDhRIGLqW+wxx8gxS+iGWC/oc8zMQAeiZaemUpCwQcwpRQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "4.0.3", + "@microsoft/1ds-core-js": "4.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.4.tgz", - "integrity": "sha512-6TlfExmErQ8Y+/ChbkyWl+jyt4wg3T6p7lwXDsUCB0LgZmlEWMaCUS0YlT73JCWmE8j7vxW8yUm0lgsgmHns3A==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.4.tgz", + "integrity": "sha512-Z4nrxYwGKP9iyrYtm7iPQXVOFy4FsEsX0nDKkAi96Qpgw+vEh6NH4ORxMMuES0EollBQ3faJyvYCwckuCVIj0g==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.4.tgz", - "integrity": "sha512-r5gWaw/K9+tKfuo2GtDiDiKASgOkPOCrKW+wZzFvuR06uuwvWjbVQ6yW/YbnfuhRF5M65ksUiMi0eCMwEOGq7Q==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.4.tgz", + "integrity": "sha512-4ms16MlIvcP4WiUPqopifNxcWCcrXQJ2ADAK/75uok2mNQe6ZNRsqb/P+pvhUxc8A5HRlvoXPP1ptDSN5Girgw==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.4.tgz", - "integrity": "sha512-anxy5kEkqBmVoEqJiJzaaXXA0wzqZi9U4zGd05xFJ04lWckP8dG3zyT3+GGdg7rDelqLTNGxndeYoFmDv63u1g==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.4.tgz", + "integrity": "sha512-MummANF0mgKIkdvVvfmHQTBliK114IZLRhTL0X0Ep+zjDwWMHqYZgew0nlFKAl6ggu42abPZFK5afpE7qjtYJA==", + "license": "MIT", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-shims": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.0.4.tgz", - "integrity": "sha512-KfoxPlLlf0JT12ADb23C5iGye/yFouoMgHEKULxkSQcYY9SsW/8rVrqqvoYKAL+u215CZU2A8Kc8sR3ehEaPCQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.4.tgz", + "integrity": "sha512-OpEPXr8vU/t/M8T9jvWJzJx/pCyygIiR1nGM/2PTde0wn7anl71Gxl5fWol7K/WwFEORNjkL3CEyWOyDc+28AA==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-channel-js": "3.0.4", - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-channel-js": "3.3.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/dynamicproto-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", - "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", + "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } }, "node_modules/@nevware21/ts-async": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.3.0.tgz", - "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.4.tgz", + "integrity": "sha512-IBTyj29GwGlxfzXw2NPnzty+w0Adx61Eze1/lknH/XIVdxtF9UnOpk76tnrHXWa6j84a1RR9hsOcHQPFv9qJjA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.10.0 < 2.x" + "@nevware21/ts-utils": ">= 0.11.6 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.10.1.tgz", - "integrity": "sha512-pMny25NnF2/MJwdqC3Iyjm2pGIXNxni4AROpcqDeWa+td9JMUY4bUS9uU9XW+BoBRqTLUL+WURF9SOd/6OQzRg==" + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.6.tgz", + "integrity": "sha512-OUUJTh3fnaUSzg9DEHgv3d7jC+DnPL65mIO7RaR+jWve7+MmcgIvF79gY97DPQ4frH+IpNR78YAYd/dW4gK3kg==", + "license": "MIT" }, "node_modules/@types/node": { "version": "20.11.24", @@ -144,13 +154,14 @@ } }, "node_modules/@vscode/extension-telemetry": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.0.tgz", - "integrity": "sha512-37RxGHXrs3GoXPgCUKQhghEu0gxs8j27RLjQwwtSf4WhPdJKz8UrqMYzpsXlliQ05zURYmtdGZst9C6+hfWXaQ==", + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.8.tgz", + "integrity": "sha512-7YcKoUvmHlIB8QYCE4FNzt3ErHi9gQPhdCM3ZWtpw1bxPT0I+lMdx52KHlzTNoJzQ2NvMX7HyzyDwBEiMgTrWQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "^4.0.3", - "@microsoft/1ds-post-js": "^4.0.3", - "@microsoft/applicationinsights-web-basic": "^3.0.4" + "@microsoft/1ds-core-js": "^4.3.4", + "@microsoft/1ds-post-js": "^4.3.4", + "@microsoft/applicationinsights-web-basic": "^3.3.4" }, "engines": { "vscode": "^1.75.0" diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 60b9718e6c38a..be411fe63a2b3 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -258,7 +258,7 @@ ] }, "dependencies": { - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "vscode-languageclient": "^10.0.0-next.13", "vscode-uri": "^3.0.8" }, diff --git a/extensions/json-language-features/package-lock.json b/extensions/json-language-features/package-lock.json index f28a8c68df027..bde80cbfbe47f 100644 --- a/extensions/json-language-features/package-lock.json +++ b/extensions/json-language-features/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "request-light": "^0.8.0", "vscode-languageclient": "^10.0.0-next.13" }, @@ -21,118 +21,128 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.0.3.tgz", - "integrity": "sha512-FrxNLVAPsAvD7+l63TlNS/Kodvpct2WulpDSn1dI4Xuy0kF4E2H867kHdwL/iY1Bj3zA3FSy/jvE4+OcDws7ug==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", + "integrity": "sha512-3gbDUQgAO8EoyQTNcAEkxpuPnioC0May13P1l1l0NKZ128L9Ts/sj8QsfwCRTjHz0HThlA+4FptcAJXNYUy3rg==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.0.3.tgz", - "integrity": "sha512-uewvmUtXKd7ttypiKQGdYI6i7UUpPkOznLayzIFrJ4r2xnG6jhPjpKRncHFXPQcM4XSWO3yf5PQ3xAbPq9t7ZQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.4.tgz", + "integrity": "sha512-nlKjWricDj0Tn68Dt0P8lX9a+X7LYrqJ6/iSfQwMfDhRIGLqW+wxx8gxS+iGWC/oc8zMQAeiZaemUpCwQcwpRQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "4.0.3", + "@microsoft/1ds-core-js": "4.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.4.tgz", - "integrity": "sha512-6TlfExmErQ8Y+/ChbkyWl+jyt4wg3T6p7lwXDsUCB0LgZmlEWMaCUS0YlT73JCWmE8j7vxW8yUm0lgsgmHns3A==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.4.tgz", + "integrity": "sha512-Z4nrxYwGKP9iyrYtm7iPQXVOFy4FsEsX0nDKkAi96Qpgw+vEh6NH4ORxMMuES0EollBQ3faJyvYCwckuCVIj0g==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.4.tgz", - "integrity": "sha512-r5gWaw/K9+tKfuo2GtDiDiKASgOkPOCrKW+wZzFvuR06uuwvWjbVQ6yW/YbnfuhRF5M65ksUiMi0eCMwEOGq7Q==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.4.tgz", + "integrity": "sha512-4ms16MlIvcP4WiUPqopifNxcWCcrXQJ2ADAK/75uok2mNQe6ZNRsqb/P+pvhUxc8A5HRlvoXPP1ptDSN5Girgw==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.4.tgz", - "integrity": "sha512-anxy5kEkqBmVoEqJiJzaaXXA0wzqZi9U4zGd05xFJ04lWckP8dG3zyT3+GGdg7rDelqLTNGxndeYoFmDv63u1g==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.4.tgz", + "integrity": "sha512-MummANF0mgKIkdvVvfmHQTBliK114IZLRhTL0X0Ep+zjDwWMHqYZgew0nlFKAl6ggu42abPZFK5afpE7qjtYJA==", + "license": "MIT", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-shims": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.0.4.tgz", - "integrity": "sha512-KfoxPlLlf0JT12ADb23C5iGye/yFouoMgHEKULxkSQcYY9SsW/8rVrqqvoYKAL+u215CZU2A8Kc8sR3ehEaPCQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.4.tgz", + "integrity": "sha512-OpEPXr8vU/t/M8T9jvWJzJx/pCyygIiR1nGM/2PTde0wn7anl71Gxl5fWol7K/WwFEORNjkL3CEyWOyDc+28AA==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-channel-js": "3.0.4", - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-channel-js": "3.3.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/dynamicproto-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", - "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", + "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } }, "node_modules/@nevware21/ts-async": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.3.0.tgz", - "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.4.tgz", + "integrity": "sha512-IBTyj29GwGlxfzXw2NPnzty+w0Adx61Eze1/lknH/XIVdxtF9UnOpk76tnrHXWa6j84a1RR9hsOcHQPFv9qJjA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.10.0 < 2.x" + "@nevware21/ts-utils": ">= 0.11.6 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.10.1.tgz", - "integrity": "sha512-pMny25NnF2/MJwdqC3Iyjm2pGIXNxni4AROpcqDeWa+td9JMUY4bUS9uU9XW+BoBRqTLUL+WURF9SOd/6OQzRg==" + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.6.tgz", + "integrity": "sha512-OUUJTh3fnaUSzg9DEHgv3d7jC+DnPL65mIO7RaR+jWve7+MmcgIvF79gY97DPQ4frH+IpNR78YAYd/dW4gK3kg==", + "license": "MIT" }, "node_modules/@types/node": { "version": "20.11.24", @@ -144,13 +154,14 @@ } }, "node_modules/@vscode/extension-telemetry": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.0.tgz", - "integrity": "sha512-37RxGHXrs3GoXPgCUKQhghEu0gxs8j27RLjQwwtSf4WhPdJKz8UrqMYzpsXlliQ05zURYmtdGZst9C6+hfWXaQ==", + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.8.tgz", + "integrity": "sha512-7YcKoUvmHlIB8QYCE4FNzt3ErHi9gQPhdCM3ZWtpw1bxPT0I+lMdx52KHlzTNoJzQ2NvMX7HyzyDwBEiMgTrWQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "^4.0.3", - "@microsoft/1ds-post-js": "^4.0.3", - "@microsoft/applicationinsights-web-basic": "^3.0.4" + "@microsoft/1ds-core-js": "^4.3.4", + "@microsoft/1ds-post-js": "^4.3.4", + "@microsoft/applicationinsights-web-basic": "^3.3.4" }, "engines": { "vscode": "^1.75.0" diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index ff620435b98bd..cf4a7f162dab0 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -167,7 +167,7 @@ ] }, "dependencies": { - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "request-light": "^0.8.0", "vscode-languageclient": "^10.0.0-next.13" }, diff --git a/extensions/markdown-language-features/package-lock.json b/extensions/markdown-language-features/package-lock.json index 91a1a6c5cb9f5..c13d2a007ad7f 100644 --- a/extensions/markdown-language-features/package-lock.json +++ b/extensions/markdown-language-features/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "dompurify": "^3.1.7", "highlight.js": "^11.8.0", "markdown-it": "^12.3.2", @@ -39,118 +39,128 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.0.3.tgz", - "integrity": "sha512-FrxNLVAPsAvD7+l63TlNS/Kodvpct2WulpDSn1dI4Xuy0kF4E2H867kHdwL/iY1Bj3zA3FSy/jvE4+OcDws7ug==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", + "integrity": "sha512-3gbDUQgAO8EoyQTNcAEkxpuPnioC0May13P1l1l0NKZ128L9Ts/sj8QsfwCRTjHz0HThlA+4FptcAJXNYUy3rg==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.0.3.tgz", - "integrity": "sha512-uewvmUtXKd7ttypiKQGdYI6i7UUpPkOznLayzIFrJ4r2xnG6jhPjpKRncHFXPQcM4XSWO3yf5PQ3xAbPq9t7ZQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.4.tgz", + "integrity": "sha512-nlKjWricDj0Tn68Dt0P8lX9a+X7LYrqJ6/iSfQwMfDhRIGLqW+wxx8gxS+iGWC/oc8zMQAeiZaemUpCwQcwpRQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "4.0.3", + "@microsoft/1ds-core-js": "4.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.4.tgz", - "integrity": "sha512-6TlfExmErQ8Y+/ChbkyWl+jyt4wg3T6p7lwXDsUCB0LgZmlEWMaCUS0YlT73JCWmE8j7vxW8yUm0lgsgmHns3A==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.4.tgz", + "integrity": "sha512-Z4nrxYwGKP9iyrYtm7iPQXVOFy4FsEsX0nDKkAi96Qpgw+vEh6NH4ORxMMuES0EollBQ3faJyvYCwckuCVIj0g==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.4.tgz", - "integrity": "sha512-r5gWaw/K9+tKfuo2GtDiDiKASgOkPOCrKW+wZzFvuR06uuwvWjbVQ6yW/YbnfuhRF5M65ksUiMi0eCMwEOGq7Q==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.4.tgz", + "integrity": "sha512-4ms16MlIvcP4WiUPqopifNxcWCcrXQJ2ADAK/75uok2mNQe6ZNRsqb/P+pvhUxc8A5HRlvoXPP1ptDSN5Girgw==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.4.tgz", - "integrity": "sha512-anxy5kEkqBmVoEqJiJzaaXXA0wzqZi9U4zGd05xFJ04lWckP8dG3zyT3+GGdg7rDelqLTNGxndeYoFmDv63u1g==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.4.tgz", + "integrity": "sha512-MummANF0mgKIkdvVvfmHQTBliK114IZLRhTL0X0Ep+zjDwWMHqYZgew0nlFKAl6ggu42abPZFK5afpE7qjtYJA==", + "license": "MIT", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-shims": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.0.4.tgz", - "integrity": "sha512-KfoxPlLlf0JT12ADb23C5iGye/yFouoMgHEKULxkSQcYY9SsW/8rVrqqvoYKAL+u215CZU2A8Kc8sR3ehEaPCQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.4.tgz", + "integrity": "sha512-OpEPXr8vU/t/M8T9jvWJzJx/pCyygIiR1nGM/2PTde0wn7anl71Gxl5fWol7K/WwFEORNjkL3CEyWOyDc+28AA==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-channel-js": "3.0.4", - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-channel-js": "3.3.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/dynamicproto-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", - "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", + "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } }, "node_modules/@nevware21/ts-async": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.3.0.tgz", - "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.4.tgz", + "integrity": "sha512-IBTyj29GwGlxfzXw2NPnzty+w0Adx61Eze1/lknH/XIVdxtF9UnOpk76tnrHXWa6j84a1RR9hsOcHQPFv9qJjA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.10.0 < 2.x" + "@nevware21/ts-utils": ">= 0.11.6 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.10.1.tgz", - "integrity": "sha512-pMny25NnF2/MJwdqC3Iyjm2pGIXNxni4AROpcqDeWa+td9JMUY4bUS9uU9XW+BoBRqTLUL+WURF9SOd/6OQzRg==" + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.6.tgz", + "integrity": "sha512-OUUJTh3fnaUSzg9DEHgv3d7jC+DnPL65mIO7RaR+jWve7+MmcgIvF79gY97DPQ4frH+IpNR78YAYd/dW4gK3kg==", + "license": "MIT" }, "node_modules/@types/dompurify": { "version": "3.0.5", @@ -223,13 +233,14 @@ "dev": true }, "node_modules/@vscode/extension-telemetry": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.0.tgz", - "integrity": "sha512-37RxGHXrs3GoXPgCUKQhghEu0gxs8j27RLjQwwtSf4WhPdJKz8UrqMYzpsXlliQ05zURYmtdGZst9C6+hfWXaQ==", + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.8.tgz", + "integrity": "sha512-7YcKoUvmHlIB8QYCE4FNzt3ErHi9gQPhdCM3ZWtpw1bxPT0I+lMdx52KHlzTNoJzQ2NvMX7HyzyDwBEiMgTrWQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "^4.0.3", - "@microsoft/1ds-post-js": "^4.0.3", - "@microsoft/applicationinsights-web-basic": "^3.0.4" + "@microsoft/1ds-core-js": "^4.3.4", + "@microsoft/1ds-post-js": "^4.3.4", + "@microsoft/applicationinsights-web-basic": "^3.3.4" }, "engines": { "vscode": "^1.75.0" diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 3f1c3a5fd5814..6ed7ff1e62e1a 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -763,7 +763,7 @@ "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "dependencies": { - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "dompurify": "^3.1.7", "highlight.js": "^11.8.0", "markdown-it": "^12.3.2", diff --git a/extensions/media-preview/package-lock.json b/extensions/media-preview/package-lock.json index 68391b8c4be9f..d26855f3ad250 100644 --- a/extensions/media-preview/package-lock.json +++ b/extensions/media-preview/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "vscode-uri": "^3.0.6" }, "engines": { @@ -17,127 +17,138 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.0.3.tgz", - "integrity": "sha512-FrxNLVAPsAvD7+l63TlNS/Kodvpct2WulpDSn1dI4Xuy0kF4E2H867kHdwL/iY1Bj3zA3FSy/jvE4+OcDws7ug==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", + "integrity": "sha512-3gbDUQgAO8EoyQTNcAEkxpuPnioC0May13P1l1l0NKZ128L9Ts/sj8QsfwCRTjHz0HThlA+4FptcAJXNYUy3rg==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.0.3.tgz", - "integrity": "sha512-uewvmUtXKd7ttypiKQGdYI6i7UUpPkOznLayzIFrJ4r2xnG6jhPjpKRncHFXPQcM4XSWO3yf5PQ3xAbPq9t7ZQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.4.tgz", + "integrity": "sha512-nlKjWricDj0Tn68Dt0P8lX9a+X7LYrqJ6/iSfQwMfDhRIGLqW+wxx8gxS+iGWC/oc8zMQAeiZaemUpCwQcwpRQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "4.0.3", + "@microsoft/1ds-core-js": "4.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.4.tgz", - "integrity": "sha512-6TlfExmErQ8Y+/ChbkyWl+jyt4wg3T6p7lwXDsUCB0LgZmlEWMaCUS0YlT73JCWmE8j7vxW8yUm0lgsgmHns3A==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.4.tgz", + "integrity": "sha512-Z4nrxYwGKP9iyrYtm7iPQXVOFy4FsEsX0nDKkAi96Qpgw+vEh6NH4ORxMMuES0EollBQ3faJyvYCwckuCVIj0g==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.4.tgz", - "integrity": "sha512-r5gWaw/K9+tKfuo2GtDiDiKASgOkPOCrKW+wZzFvuR06uuwvWjbVQ6yW/YbnfuhRF5M65ksUiMi0eCMwEOGq7Q==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.4.tgz", + "integrity": "sha512-4ms16MlIvcP4WiUPqopifNxcWCcrXQJ2ADAK/75uok2mNQe6ZNRsqb/P+pvhUxc8A5HRlvoXPP1ptDSN5Girgw==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.4.tgz", - "integrity": "sha512-anxy5kEkqBmVoEqJiJzaaXXA0wzqZi9U4zGd05xFJ04lWckP8dG3zyT3+GGdg7rDelqLTNGxndeYoFmDv63u1g==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.4.tgz", + "integrity": "sha512-MummANF0mgKIkdvVvfmHQTBliK114IZLRhTL0X0Ep+zjDwWMHqYZgew0nlFKAl6ggu42abPZFK5afpE7qjtYJA==", + "license": "MIT", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-shims": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.0.4.tgz", - "integrity": "sha512-KfoxPlLlf0JT12ADb23C5iGye/yFouoMgHEKULxkSQcYY9SsW/8rVrqqvoYKAL+u215CZU2A8Kc8sR3ehEaPCQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.4.tgz", + "integrity": "sha512-OpEPXr8vU/t/M8T9jvWJzJx/pCyygIiR1nGM/2PTde0wn7anl71Gxl5fWol7K/WwFEORNjkL3CEyWOyDc+28AA==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-channel-js": "3.0.4", - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-channel-js": "3.3.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/dynamicproto-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", - "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", + "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } }, "node_modules/@nevware21/ts-async": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.3.0.tgz", - "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.4.tgz", + "integrity": "sha512-IBTyj29GwGlxfzXw2NPnzty+w0Adx61Eze1/lknH/XIVdxtF9UnOpk76tnrHXWa6j84a1RR9hsOcHQPFv9qJjA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.10.0 < 2.x" + "@nevware21/ts-utils": ">= 0.11.6 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.10.1.tgz", - "integrity": "sha512-pMny25NnF2/MJwdqC3Iyjm2pGIXNxni4AROpcqDeWa+td9JMUY4bUS9uU9XW+BoBRqTLUL+WURF9SOd/6OQzRg==" + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.6.tgz", + "integrity": "sha512-OUUJTh3fnaUSzg9DEHgv3d7jC+DnPL65mIO7RaR+jWve7+MmcgIvF79gY97DPQ4frH+IpNR78YAYd/dW4gK3kg==", + "license": "MIT" }, "node_modules/@vscode/extension-telemetry": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.0.tgz", - "integrity": "sha512-37RxGHXrs3GoXPgCUKQhghEu0gxs8j27RLjQwwtSf4WhPdJKz8UrqMYzpsXlliQ05zURYmtdGZst9C6+hfWXaQ==", + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.8.tgz", + "integrity": "sha512-7YcKoUvmHlIB8QYCE4FNzt3ErHi9gQPhdCM3ZWtpw1bxPT0I+lMdx52KHlzTNoJzQ2NvMX7HyzyDwBEiMgTrWQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "^4.0.3", - "@microsoft/1ds-post-js": "^4.0.3", - "@microsoft/applicationinsights-web-basic": "^3.0.4" + "@microsoft/1ds-core-js": "^4.3.4", + "@microsoft/1ds-post-js": "^4.3.4", + "@microsoft/applicationinsights-web-basic": "^3.3.4" }, "engines": { "vscode": "^1.75.0" diff --git a/extensions/media-preview/package.json b/extensions/media-preview/package.json index b42256a226010..e7ddad4354e57 100644 --- a/extensions/media-preview/package.json +++ b/extensions/media-preview/package.json @@ -126,7 +126,7 @@ "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "dependencies": { - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "vscode-uri": "^3.0.6" }, "repository": { diff --git a/extensions/merge-conflict/package-lock.json b/extensions/merge-conflict/package-lock.json index a57272606cd07..5ee68d290f010 100644 --- a/extensions/merge-conflict/package-lock.json +++ b/extensions/merge-conflict/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@vscode/extension-telemetry": "^0.9.0" + "@vscode/extension-telemetry": "^0.9.8" }, "devDependencies": { "@types/node": "20.x" @@ -19,118 +19,128 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.0.3.tgz", - "integrity": "sha512-FrxNLVAPsAvD7+l63TlNS/Kodvpct2WulpDSn1dI4Xuy0kF4E2H867kHdwL/iY1Bj3zA3FSy/jvE4+OcDws7ug==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", + "integrity": "sha512-3gbDUQgAO8EoyQTNcAEkxpuPnioC0May13P1l1l0NKZ128L9Ts/sj8QsfwCRTjHz0HThlA+4FptcAJXNYUy3rg==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.0.3.tgz", - "integrity": "sha512-uewvmUtXKd7ttypiKQGdYI6i7UUpPkOznLayzIFrJ4r2xnG6jhPjpKRncHFXPQcM4XSWO3yf5PQ3xAbPq9t7ZQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.4.tgz", + "integrity": "sha512-nlKjWricDj0Tn68Dt0P8lX9a+X7LYrqJ6/iSfQwMfDhRIGLqW+wxx8gxS+iGWC/oc8zMQAeiZaemUpCwQcwpRQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "4.0.3", + "@microsoft/1ds-core-js": "4.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.4.tgz", - "integrity": "sha512-6TlfExmErQ8Y+/ChbkyWl+jyt4wg3T6p7lwXDsUCB0LgZmlEWMaCUS0YlT73JCWmE8j7vxW8yUm0lgsgmHns3A==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.4.tgz", + "integrity": "sha512-Z4nrxYwGKP9iyrYtm7iPQXVOFy4FsEsX0nDKkAi96Qpgw+vEh6NH4ORxMMuES0EollBQ3faJyvYCwckuCVIj0g==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.4.tgz", - "integrity": "sha512-r5gWaw/K9+tKfuo2GtDiDiKASgOkPOCrKW+wZzFvuR06uuwvWjbVQ6yW/YbnfuhRF5M65ksUiMi0eCMwEOGq7Q==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.4.tgz", + "integrity": "sha512-4ms16MlIvcP4WiUPqopifNxcWCcrXQJ2ADAK/75uok2mNQe6ZNRsqb/P+pvhUxc8A5HRlvoXPP1ptDSN5Girgw==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.4.tgz", - "integrity": "sha512-anxy5kEkqBmVoEqJiJzaaXXA0wzqZi9U4zGd05xFJ04lWckP8dG3zyT3+GGdg7rDelqLTNGxndeYoFmDv63u1g==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.4.tgz", + "integrity": "sha512-MummANF0mgKIkdvVvfmHQTBliK114IZLRhTL0X0Ep+zjDwWMHqYZgew0nlFKAl6ggu42abPZFK5afpE7qjtYJA==", + "license": "MIT", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-shims": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.0.4.tgz", - "integrity": "sha512-KfoxPlLlf0JT12ADb23C5iGye/yFouoMgHEKULxkSQcYY9SsW/8rVrqqvoYKAL+u215CZU2A8Kc8sR3ehEaPCQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.4.tgz", + "integrity": "sha512-OpEPXr8vU/t/M8T9jvWJzJx/pCyygIiR1nGM/2PTde0wn7anl71Gxl5fWol7K/WwFEORNjkL3CEyWOyDc+28AA==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-channel-js": "3.0.4", - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-channel-js": "3.3.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/dynamicproto-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", - "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", + "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } }, "node_modules/@nevware21/ts-async": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.3.0.tgz", - "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.4.tgz", + "integrity": "sha512-IBTyj29GwGlxfzXw2NPnzty+w0Adx61Eze1/lknH/XIVdxtF9UnOpk76tnrHXWa6j84a1RR9hsOcHQPFv9qJjA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.10.0 < 2.x" + "@nevware21/ts-utils": ">= 0.11.6 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.10.1.tgz", - "integrity": "sha512-pMny25NnF2/MJwdqC3Iyjm2pGIXNxni4AROpcqDeWa+td9JMUY4bUS9uU9XW+BoBRqTLUL+WURF9SOd/6OQzRg==" + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.6.tgz", + "integrity": "sha512-OUUJTh3fnaUSzg9DEHgv3d7jC+DnPL65mIO7RaR+jWve7+MmcgIvF79gY97DPQ4frH+IpNR78YAYd/dW4gK3kg==", + "license": "MIT" }, "node_modules/@types/node": { "version": "20.11.24", @@ -142,13 +152,14 @@ } }, "node_modules/@vscode/extension-telemetry": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.0.tgz", - "integrity": "sha512-37RxGHXrs3GoXPgCUKQhghEu0gxs8j27RLjQwwtSf4WhPdJKz8UrqMYzpsXlliQ05zURYmtdGZst9C6+hfWXaQ==", + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.8.tgz", + "integrity": "sha512-7YcKoUvmHlIB8QYCE4FNzt3ErHi9gQPhdCM3ZWtpw1bxPT0I+lMdx52KHlzTNoJzQ2NvMX7HyzyDwBEiMgTrWQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "^4.0.3", - "@microsoft/1ds-post-js": "^4.0.3", - "@microsoft/applicationinsights-web-basic": "^3.0.4" + "@microsoft/1ds-core-js": "^4.3.4", + "@microsoft/1ds-post-js": "^4.3.4", + "@microsoft/applicationinsights-web-basic": "^3.3.4" }, "engines": { "vscode": "^1.75.0" diff --git a/extensions/merge-conflict/package.json b/extensions/merge-conflict/package.json index cdda46fab3228..de56c9c22cfec 100644 --- a/extensions/merge-conflict/package.json +++ b/extensions/merge-conflict/package.json @@ -166,7 +166,7 @@ } }, "dependencies": { - "@vscode/extension-telemetry": "^0.9.0" + "@vscode/extension-telemetry": "^0.9.8" }, "devDependencies": { "@types/node": "20.x" diff --git a/extensions/microsoft-authentication/package-lock.json b/extensions/microsoft-authentication/package-lock.json index 9f69f13972cb2..4aae40c5f1704 100644 --- a/extensions/microsoft-authentication/package-lock.json +++ b/extensions/microsoft-authentication/package-lock.json @@ -12,7 +12,7 @@ "@azure/ms-rest-azure-env": "^2.0.0", "@azure/msal-node": "^2.16.2", "@azure/msal-node-extensions": "^1.5.0", - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "keytar": "file:./packageMocks/keytar", "vscode-tas-client": "^0.1.84" }, @@ -78,118 +78,128 @@ "license": "MIT" }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.0.3.tgz", - "integrity": "sha512-FrxNLVAPsAvD7+l63TlNS/Kodvpct2WulpDSn1dI4Xuy0kF4E2H867kHdwL/iY1Bj3zA3FSy/jvE4+OcDws7ug==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", + "integrity": "sha512-3gbDUQgAO8EoyQTNcAEkxpuPnioC0May13P1l1l0NKZ128L9Ts/sj8QsfwCRTjHz0HThlA+4FptcAJXNYUy3rg==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.0.3.tgz", - "integrity": "sha512-uewvmUtXKd7ttypiKQGdYI6i7UUpPkOznLayzIFrJ4r2xnG6jhPjpKRncHFXPQcM4XSWO3yf5PQ3xAbPq9t7ZQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.4.tgz", + "integrity": "sha512-nlKjWricDj0Tn68Dt0P8lX9a+X7LYrqJ6/iSfQwMfDhRIGLqW+wxx8gxS+iGWC/oc8zMQAeiZaemUpCwQcwpRQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "4.0.3", + "@microsoft/1ds-core-js": "4.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.4.tgz", - "integrity": "sha512-6TlfExmErQ8Y+/ChbkyWl+jyt4wg3T6p7lwXDsUCB0LgZmlEWMaCUS0YlT73JCWmE8j7vxW8yUm0lgsgmHns3A==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.4.tgz", + "integrity": "sha512-Z4nrxYwGKP9iyrYtm7iPQXVOFy4FsEsX0nDKkAi96Qpgw+vEh6NH4ORxMMuES0EollBQ3faJyvYCwckuCVIj0g==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.4.tgz", - "integrity": "sha512-r5gWaw/K9+tKfuo2GtDiDiKASgOkPOCrKW+wZzFvuR06uuwvWjbVQ6yW/YbnfuhRF5M65ksUiMi0eCMwEOGq7Q==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.4.tgz", + "integrity": "sha512-4ms16MlIvcP4WiUPqopifNxcWCcrXQJ2ADAK/75uok2mNQe6ZNRsqb/P+pvhUxc8A5HRlvoXPP1ptDSN5Girgw==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.4.tgz", - "integrity": "sha512-anxy5kEkqBmVoEqJiJzaaXXA0wzqZi9U4zGd05xFJ04lWckP8dG3zyT3+GGdg7rDelqLTNGxndeYoFmDv63u1g==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.4.tgz", + "integrity": "sha512-MummANF0mgKIkdvVvfmHQTBliK114IZLRhTL0X0Ep+zjDwWMHqYZgew0nlFKAl6ggu42abPZFK5afpE7qjtYJA==", + "license": "MIT", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-shims": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.0.4.tgz", - "integrity": "sha512-KfoxPlLlf0JT12ADb23C5iGye/yFouoMgHEKULxkSQcYY9SsW/8rVrqqvoYKAL+u215CZU2A8Kc8sR3ehEaPCQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.4.tgz", + "integrity": "sha512-OpEPXr8vU/t/M8T9jvWJzJx/pCyygIiR1nGM/2PTde0wn7anl71Gxl5fWol7K/WwFEORNjkL3CEyWOyDc+28AA==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-channel-js": "3.0.4", - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-channel-js": "3.3.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/dynamicproto-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", - "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", + "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } }, "node_modules/@nevware21/ts-async": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.3.0.tgz", - "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.4.tgz", + "integrity": "sha512-IBTyj29GwGlxfzXw2NPnzty+w0Adx61Eze1/lknH/XIVdxtF9UnOpk76tnrHXWa6j84a1RR9hsOcHQPFv9qJjA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.10.0 < 2.x" + "@nevware21/ts-utils": ">= 0.11.6 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.10.1.tgz", - "integrity": "sha512-pMny25NnF2/MJwdqC3Iyjm2pGIXNxni4AROpcqDeWa+td9JMUY4bUS9uU9XW+BoBRqTLUL+WURF9SOd/6OQzRg==" + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.6.tgz", + "integrity": "sha512-OUUJTh3fnaUSzg9DEHgv3d7jC+DnPL65mIO7RaR+jWve7+MmcgIvF79gY97DPQ4frH+IpNR78YAYd/dW4gK3kg==", + "license": "MIT" }, "node_modules/@types/node": { "version": "20.11.24", @@ -235,13 +245,14 @@ "dev": true }, "node_modules/@vscode/extension-telemetry": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.0.tgz", - "integrity": "sha512-37RxGHXrs3GoXPgCUKQhghEu0gxs8j27RLjQwwtSf4WhPdJKz8UrqMYzpsXlliQ05zURYmtdGZst9C6+hfWXaQ==", + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.8.tgz", + "integrity": "sha512-7YcKoUvmHlIB8QYCE4FNzt3ErHi9gQPhdCM3ZWtpw1bxPT0I+lMdx52KHlzTNoJzQ2NvMX7HyzyDwBEiMgTrWQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "^4.0.3", - "@microsoft/1ds-post-js": "^4.0.3", - "@microsoft/applicationinsights-web-basic": "^3.0.4" + "@microsoft/1ds-core-js": "^4.3.4", + "@microsoft/1ds-post-js": "^4.3.4", + "@microsoft/applicationinsights-web-basic": "^3.3.4" }, "engines": { "vscode": "^1.75.0" diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index c8ad1c070e267..ba7d83aa359eb 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -142,7 +142,7 @@ "@azure/ms-rest-azure-env": "^2.0.0", "@azure/msal-node": "^2.16.2", "@azure/msal-node-extensions": "^1.5.0", - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "keytar": "file:./packageMocks/keytar", "vscode-tas-client": "^0.1.84" }, diff --git a/extensions/simple-browser/package-lock.json b/extensions/simple-browser/package-lock.json index d3542946e63aa..c6d9b23636a95 100644 --- a/extensions/simple-browser/package-lock.json +++ b/extensions/simple-browser/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@vscode/extension-telemetry": "^0.9.0" + "@vscode/extension-telemetry": "^0.9.8" }, "devDependencies": { "@types/vscode-webview": "^1.57.0", @@ -20,118 +20,128 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.0.3.tgz", - "integrity": "sha512-FrxNLVAPsAvD7+l63TlNS/Kodvpct2WulpDSn1dI4Xuy0kF4E2H867kHdwL/iY1Bj3zA3FSy/jvE4+OcDws7ug==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", + "integrity": "sha512-3gbDUQgAO8EoyQTNcAEkxpuPnioC0May13P1l1l0NKZ128L9Ts/sj8QsfwCRTjHz0HThlA+4FptcAJXNYUy3rg==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.0.3.tgz", - "integrity": "sha512-uewvmUtXKd7ttypiKQGdYI6i7UUpPkOznLayzIFrJ4r2xnG6jhPjpKRncHFXPQcM4XSWO3yf5PQ3xAbPq9t7ZQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.4.tgz", + "integrity": "sha512-nlKjWricDj0Tn68Dt0P8lX9a+X7LYrqJ6/iSfQwMfDhRIGLqW+wxx8gxS+iGWC/oc8zMQAeiZaemUpCwQcwpRQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "4.0.3", + "@microsoft/1ds-core-js": "4.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.4.tgz", - "integrity": "sha512-6TlfExmErQ8Y+/ChbkyWl+jyt4wg3T6p7lwXDsUCB0LgZmlEWMaCUS0YlT73JCWmE8j7vxW8yUm0lgsgmHns3A==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.4.tgz", + "integrity": "sha512-Z4nrxYwGKP9iyrYtm7iPQXVOFy4FsEsX0nDKkAi96Qpgw+vEh6NH4ORxMMuES0EollBQ3faJyvYCwckuCVIj0g==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.4.tgz", - "integrity": "sha512-r5gWaw/K9+tKfuo2GtDiDiKASgOkPOCrKW+wZzFvuR06uuwvWjbVQ6yW/YbnfuhRF5M65ksUiMi0eCMwEOGq7Q==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.4.tgz", + "integrity": "sha512-4ms16MlIvcP4WiUPqopifNxcWCcrXQJ2ADAK/75uok2mNQe6ZNRsqb/P+pvhUxc8A5HRlvoXPP1ptDSN5Girgw==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.4.tgz", - "integrity": "sha512-anxy5kEkqBmVoEqJiJzaaXXA0wzqZi9U4zGd05xFJ04lWckP8dG3zyT3+GGdg7rDelqLTNGxndeYoFmDv63u1g==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.4.tgz", + "integrity": "sha512-MummANF0mgKIkdvVvfmHQTBliK114IZLRhTL0X0Ep+zjDwWMHqYZgew0nlFKAl6ggu42abPZFK5afpE7qjtYJA==", + "license": "MIT", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-shims": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.0.4.tgz", - "integrity": "sha512-KfoxPlLlf0JT12ADb23C5iGye/yFouoMgHEKULxkSQcYY9SsW/8rVrqqvoYKAL+u215CZU2A8Kc8sR3ehEaPCQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.4.tgz", + "integrity": "sha512-OpEPXr8vU/t/M8T9jvWJzJx/pCyygIiR1nGM/2PTde0wn7anl71Gxl5fWol7K/WwFEORNjkL3CEyWOyDc+28AA==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-channel-js": "3.0.4", - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-channel-js": "3.3.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/dynamicproto-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", - "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", + "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } }, "node_modules/@nevware21/ts-async": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.3.0.tgz", - "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.4.tgz", + "integrity": "sha512-IBTyj29GwGlxfzXw2NPnzty+w0Adx61Eze1/lknH/XIVdxtF9UnOpk76tnrHXWa6j84a1RR9hsOcHQPFv9qJjA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.10.0 < 2.x" + "@nevware21/ts-utils": ">= 0.11.6 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.10.1.tgz", - "integrity": "sha512-pMny25NnF2/MJwdqC3Iyjm2pGIXNxni4AROpcqDeWa+td9JMUY4bUS9uU9XW+BoBRqTLUL+WURF9SOd/6OQzRg==" + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.6.tgz", + "integrity": "sha512-OUUJTh3fnaUSzg9DEHgv3d7jC+DnPL65mIO7RaR+jWve7+MmcgIvF79gY97DPQ4frH+IpNR78YAYd/dW4gK3kg==", + "license": "MIT" }, "node_modules/@types/vscode-webview": { "version": "1.57.0", @@ -147,13 +157,14 @@ "license": "CC-BY-4.0" }, "node_modules/@vscode/extension-telemetry": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.0.tgz", - "integrity": "sha512-37RxGHXrs3GoXPgCUKQhghEu0gxs8j27RLjQwwtSf4WhPdJKz8UrqMYzpsXlliQ05zURYmtdGZst9C6+hfWXaQ==", + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.8.tgz", + "integrity": "sha512-7YcKoUvmHlIB8QYCE4FNzt3ErHi9gQPhdCM3ZWtpw1bxPT0I+lMdx52KHlzTNoJzQ2NvMX7HyzyDwBEiMgTrWQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "^4.0.3", - "@microsoft/1ds-post-js": "^4.0.3", - "@microsoft/applicationinsights-web-basic": "^3.0.4" + "@microsoft/1ds-core-js": "^4.3.4", + "@microsoft/1ds-post-js": "^4.3.4", + "@microsoft/applicationinsights-web-basic": "^3.3.4" }, "engines": { "vscode": "^1.75.0" diff --git a/extensions/simple-browser/package.json b/extensions/simple-browser/package.json index 0d812db6078ab..9aba9ad25036a 100644 --- a/extensions/simple-browser/package.json +++ b/extensions/simple-browser/package.json @@ -66,7 +66,7 @@ "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "dependencies": { - "@vscode/extension-telemetry": "^0.9.0" + "@vscode/extension-telemetry": "^0.9.8" }, "devDependencies": { "@types/vscode-webview": "^1.57.0", diff --git a/extensions/typescript-language-features/package-lock.json b/extensions/typescript-language-features/package-lock.json index 6a19dce700f2c..4f97eb1048c48 100644 --- a/extensions/typescript-language-features/package-lock.json +++ b/extensions/typescript-language-features/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "@vscode/sync-api-client": "^0.7.2", "@vscode/sync-api-common": "^0.7.2", "@vscode/sync-api-service": "^0.7.3", @@ -28,118 +28,128 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.0.3.tgz", - "integrity": "sha512-FrxNLVAPsAvD7+l63TlNS/Kodvpct2WulpDSn1dI4Xuy0kF4E2H867kHdwL/iY1Bj3zA3FSy/jvE4+OcDws7ug==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", + "integrity": "sha512-3gbDUQgAO8EoyQTNcAEkxpuPnioC0May13P1l1l0NKZ128L9Ts/sj8QsfwCRTjHz0HThlA+4FptcAJXNYUy3rg==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.0.3.tgz", - "integrity": "sha512-uewvmUtXKd7ttypiKQGdYI6i7UUpPkOznLayzIFrJ4r2xnG6jhPjpKRncHFXPQcM4XSWO3yf5PQ3xAbPq9t7ZQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.4.tgz", + "integrity": "sha512-nlKjWricDj0Tn68Dt0P8lX9a+X7LYrqJ6/iSfQwMfDhRIGLqW+wxx8gxS+iGWC/oc8zMQAeiZaemUpCwQcwpRQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "4.0.3", + "@microsoft/1ds-core-js": "4.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.4.tgz", - "integrity": "sha512-6TlfExmErQ8Y+/ChbkyWl+jyt4wg3T6p7lwXDsUCB0LgZmlEWMaCUS0YlT73JCWmE8j7vxW8yUm0lgsgmHns3A==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.4.tgz", + "integrity": "sha512-Z4nrxYwGKP9iyrYtm7iPQXVOFy4FsEsX0nDKkAi96Qpgw+vEh6NH4ORxMMuES0EollBQ3faJyvYCwckuCVIj0g==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.4.tgz", - "integrity": "sha512-r5gWaw/K9+tKfuo2GtDiDiKASgOkPOCrKW+wZzFvuR06uuwvWjbVQ6yW/YbnfuhRF5M65ksUiMi0eCMwEOGq7Q==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.4.tgz", + "integrity": "sha512-4ms16MlIvcP4WiUPqopifNxcWCcrXQJ2ADAK/75uok2mNQe6ZNRsqb/P+pvhUxc8A5HRlvoXPP1ptDSN5Girgw==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.4.tgz", - "integrity": "sha512-anxy5kEkqBmVoEqJiJzaaXXA0wzqZi9U4zGd05xFJ04lWckP8dG3zyT3+GGdg7rDelqLTNGxndeYoFmDv63u1g==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.4.tgz", + "integrity": "sha512-MummANF0mgKIkdvVvfmHQTBliK114IZLRhTL0X0Ep+zjDwWMHqYZgew0nlFKAl6ggu42abPZFK5afpE7qjtYJA==", + "license": "MIT", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-shims": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.0.4.tgz", - "integrity": "sha512-KfoxPlLlf0JT12ADb23C5iGye/yFouoMgHEKULxkSQcYY9SsW/8rVrqqvoYKAL+u215CZU2A8Kc8sR3ehEaPCQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.4.tgz", + "integrity": "sha512-OpEPXr8vU/t/M8T9jvWJzJx/pCyygIiR1nGM/2PTde0wn7anl71Gxl5fWol7K/WwFEORNjkL3CEyWOyDc+28AA==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-channel-js": "3.0.4", - "@microsoft/applicationinsights-common": "3.0.4", - "@microsoft/applicationinsights-core-js": "3.0.4", + "@microsoft/applicationinsights-channel-js": "3.3.4", + "@microsoft/applicationinsights-common": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.3.4", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.2 < 2.x", + "@nevware21/ts-utils": ">= 0.11.3 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/dynamicproto-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", - "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", + "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } }, "node_modules/@nevware21/ts-async": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.3.0.tgz", - "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.4.tgz", + "integrity": "sha512-IBTyj29GwGlxfzXw2NPnzty+w0Adx61Eze1/lknH/XIVdxtF9UnOpk76tnrHXWa6j84a1RR9hsOcHQPFv9qJjA==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.10.0 < 2.x" + "@nevware21/ts-utils": ">= 0.11.6 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.10.1.tgz", - "integrity": "sha512-pMny25NnF2/MJwdqC3Iyjm2pGIXNxni4AROpcqDeWa+td9JMUY4bUS9uU9XW+BoBRqTLUL+WURF9SOd/6OQzRg==" + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.6.tgz", + "integrity": "sha512-OUUJTh3fnaUSzg9DEHgv3d7jC+DnPL65mIO7RaR+jWve7+MmcgIvF79gY97DPQ4frH+IpNR78YAYd/dW4gK3kg==", + "license": "MIT" }, "node_modules/@types/node": { "version": "20.11.24", @@ -157,13 +167,14 @@ "dev": true }, "node_modules/@vscode/extension-telemetry": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.0.tgz", - "integrity": "sha512-37RxGHXrs3GoXPgCUKQhghEu0gxs8j27RLjQwwtSf4WhPdJKz8UrqMYzpsXlliQ05zURYmtdGZst9C6+hfWXaQ==", + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.8.tgz", + "integrity": "sha512-7YcKoUvmHlIB8QYCE4FNzt3ErHi9gQPhdCM3ZWtpw1bxPT0I+lMdx52KHlzTNoJzQ2NvMX7HyzyDwBEiMgTrWQ==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "^4.0.3", - "@microsoft/1ds-post-js": "^4.0.3", - "@microsoft/applicationinsights-web-basic": "^3.0.4" + "@microsoft/1ds-core-js": "^4.3.4", + "@microsoft/1ds-post-js": "^4.3.4", + "@microsoft/applicationinsights-web-basic": "^3.3.4" }, "engines": { "vscode": "^1.75.0" diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index c6363dfc48e16..65038311f76b2 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -40,7 +40,7 @@ "Programming Languages" ], "dependencies": { - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^0.9.8", "@vscode/sync-api-client": "^0.7.2", "@vscode/sync-api-common": "^0.7.2", "@vscode/sync-api-service": "^0.7.3", From 1649b305c65c99dcc7b48ee1bf92453555f73203 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 17 Dec 2024 11:13:06 -0800 Subject: [PATCH 056/200] testing: fix can toggle inline test coverage for non-text files outside the workspace (#236386) Fixes #235346 --- .../contrib/testing/browser/codeCoverageDecorations.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts index 0d85cf22e3ab2..f82948d310604 100644 --- a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts @@ -39,9 +39,8 @@ import { KeybindingWeight } from '../../../../platform/keybinding/common/keybind import { ILogService } from '../../../../platform/log/common/log.js'; import { bindContextKey, observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; import { IQuickInputService, QuickPickInput } from '../../../../platform/quickinput/common/quickInput.js'; -import * as coverUtils from './codeCoverageDisplayUtils.js'; -import { testingCoverageMissingBranch, testingCoverageReport, testingFilterIcon, testingRerunIcon } from './icons.js'; -import { ManagedTestCoverageBars } from './testCoverageBars.js'; +import { ActiveEditorContext } from '../../../common/contextkeys.js'; +import { TEXT_FILE_EDITOR_ID } from '../../files/common/files.js'; import { getTestingConfiguration, TestingConfigKeys } from '../common/configuration.js'; import { TestCommandId } from '../common/constants.js'; import { FileCoverage } from '../common/testCoverage.js'; @@ -50,6 +49,9 @@ import { TestId } from '../common/testId.js'; import { ITestService } from '../common/testService.js'; import { CoverageDetails, DetailType, IDeclarationCoverage, IStatementCoverage } from '../common/testTypes.js'; import { TestingContextKeys } from '../common/testingContextKeys.js'; +import * as coverUtils from './codeCoverageDisplayUtils.js'; +import { testingCoverageMissingBranch, testingCoverageReport, testingFilterIcon, testingRerunIcon } from './icons.js'; +import { ManagedTestCoverageBars } from './testCoverageBars.js'; const CLASS_HIT = 'coverage-deco-hit'; const CLASS_MISS = 'coverage-deco-miss'; @@ -772,6 +774,7 @@ registerAction2(class FilterCoverageToTestInEditor extends Action2 { TestingContextKeys.isTestCoverageOpen, TestingContextKeys.coverageToolbarEnabled.notEqualsTo(true), TestingContextKeys.hasPerTestCoverage, + ActiveEditorContext.isEqualTo(TEXT_FILE_EDITOR_ID), ), group: 'navigation', }, From 9bb364f3328e5fda91c2e3b77a557e2927653030 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 17 Dec 2024 11:17:01 -0800 Subject: [PATCH 057/200] testing: fix scrollbar overlaps coverage indicators (#236387) Fixes #235343 --- src/vs/workbench/contrib/testing/browser/media/testing.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index 2ebd1fc35bca5..d56ac88a18513 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -481,7 +481,7 @@ align-items: center; gap: 4px; font-size: 11px; - margin-right: 0.8em; + margin-right: 12px; } .test-coverage-bars .bar { From 6baeecd419a5197b1077d3a8e5e16e922c558258 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 17 Dec 2024 11:23:55 -0800 Subject: [PATCH 058/200] Add my labels (#236391) --- .vscode/notebooks/grooming.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/grooming.github-issues b/.vscode/notebooks/grooming.github-issues index e408bbd74215a..6d9deb71792d9 100644 --- a/.vscode/notebooks/grooming.github-issues +++ b/.vscode/notebooks/grooming.github-issues @@ -27,7 +27,7 @@ { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode assignee:$assignee is:open type:issue -label:\"info-needed\" -label:api -label:api-finalization -label:api-proposal -label:authentication -label:bisect-ext -label:bracket-pair-colorization -label:bracket-pair-guides -label:breadcrumbs -label:callhierarchy -label:chrome-devtools -label:cloud-changes -label:code-lens -label:command-center -label:comments -label:config -label:containers -label:context-keys -label:continue-working-on -label:css-less-scss -label:custom-editors -label:debug -label:debug-disassembly -label:dialogs -label:diff-editor -label:dropdown -label:editor-api -label:editor-autoclosing -label:editor-autoindent -label:editor-bracket-matching -label:editor-clipboard -label:editor-code-actions -label:editor-color-picker -label:editor-columnselect -label:editor-commands -label:editor-comments -label:editor-contrib -label:editor-core -label:editor-drag-and-drop -label:editor-error-widget -label:editor-find -label:editor-folding -label:editor-highlight -label:editor-hover -label:editor-indent-detection -label:editor-indent-guides -label:editor-input -label:editor-input-IME -label:editor-insets -label:editor-minimap -label:editor-multicursor -label:editor-parameter-hints -label:editor-render-whitespace -label:editor-rendering -label:editor-widgets -label:editor-RTL -label:editor-scrollbar -label:editor-sorting -label:editor-sticky-scroll -label:editor-symbols -label:editor-synced-region -label:editor-textbuffer -label:editor-theming -label:editor-wordnav -label:editor-wrapping -label:emmet -label:emmet-parse -label:error-list -label:extension-activation -label:extension-host -label:extension-prerelease -label:extension-recommendations -label:extensions -label:extensions-development -label:file-decorations -label:file-encoding -label:file-explorer -label:file-glob -label:file-io -label:file-nesting -label:file-watcher -label:font-rendering -label:formatting -label:getting-started -label:ghost-text -label:git -label:github -label:github-repositories -label:gpu -label:grammar -label:grid-widget -label:html -label:icon-brand -label:icons-product -label:image-preview -label:inlay-hints -label:inline-completions -label:install-update -label:intellisense-config -label:interactive-playground -label:interactive-window -label:issue-bot -label:issue-reporter -label:javascript -label:json -label:keybindings -label:keybindings-editor -label:keyboard-layout -label:chat -label:l10n-platform -label:label-provider -label:languages-basic -label:languages-diagnostics -label:languages-guessing -label:layout -label:lcd-text-rendering -label:list-widget -label:live-preview -label:log -label:markdown -label:marketplace -label:menus -label:merge-conflict -label:merge-editor -label:merge-editor-workbench -label:monaco-editor -label:native-file-dialog -label:network -label:notebook -label:notebook-accessibility -label:notebook-api -label:notebook-cell-editor -label:notebook-celltoolbar -label:notebook-clipboard -label:notebook-commands -label:notebook-debugging -label:notebook-diff -label:notebook-dnd -label:notebook-execution -label:notebook-find -label:notebook-folding -label:notebook-getting-started -label:notebook-globaltoolbar -label:notebook-ipynb -label:notebook-kernel -label:notebook-kernel-picker -label:notebook-language -label:notebook-layout -label:notebook-markdown -label:notebook-output -label:notebook-perf -label:notebook-remote -label:notebook-serialization -label:notebook-statusbar -label:notebook-toc-outline -label:notebook-undo-redo -label:notebook-variables -label:notebook-workbench-integration -label:notebook-workflow -label:notebook-sticky-scroll -label:notebook-format -label:notebook-code-actions -label:open-editors -label:opener -label:outline -label:output -label:packaging -label:perf -label:perf-bloat -label:perf-startup -label:php -label:portable-mode -label:proxy -label:quick-open -label:quick-pick -label:references-viewlet -label:release-notes -label:remote -label:remote-connection -label:remote-explorer -label:remote-tunnel -label:rename -label:runCommands -label:sandbox -label:sash-widget -label:scm -label:screencast-mode -label:search -label:search-api -label:search-editor -label:search-replace -label:semantic-tokens -label:server -label:settings-editor -label:settings-sync -label:settings-sync-server -label:shared-process -label:simple-file-dialog -label:smart-select -label:snap -label:snippets -label:splitview-widget -label:ssh -label:suggest -label:table-widget -label:tasks -label:telemetry -label:terminal -label:terminal-accessibility -label:terminal-conpty -label:terminal-editors -label:terminal-external -label:terminal-find -label:terminal-input -label:terminal-layout -label:terminal-links -label:terminal-local-echo -label:terminal-persistence -label:terminal-process -label:terminal-profiles -label:terminal-quick-fix -label:terminal-rendering -label:terminal-shell-bash -label:terminal-shell-cmd -label:terminal-shell-fish -label:terminal-shell-git-bash -label:terminal-shell-integration -label:terminal-shell-pwsh -label:terminal-shell-zsh -label:terminal-sticky-scroll -label:terminal-tabs -label:testing -label:themes -label:timeline -label:timeline-git -label:timeline-local-history -label:titlebar -label:tokenization -label:touch/pointer -label:trackpad/scroll -label:tree-views -label:tree-widget -label:typescript -label:undo-redo -label:unicode-highlight -label:uri -label:user-profiles -label:ux -label:variable-resolving -label:VIM -label:virtual-workspaces -label:vscode-website -label:vscode.dev -label:web -label:webview -label:webview-views -label:workbench-actions -label:workbench-banner -label:workbench-cli -label:workbench-diagnostics -label:workbench-dnd -label:workbench-editor-grid -label:workbench-editor-groups -label:workbench-editor-resolver -label:workbench-editors -label:workbench-electron -label:workbench-fonts -label:workbench-history -label:workbench-hot-exit -label:workbench-hover -label:workbench-launch -label:workbench-link -label:workbench-multiroot -label:workbench-notifications -label:workbench-os-integration -label:workbench-rapid-render -label:workbench-run-as-admin -label:workbench-state -label:workbench-status -label:workbench-tabs -label:workbench-touchbar -label:workbench-untitled-editors -label:workbench-views -label:workbench-welcome -label:workbench-window -label:workbench-workspace -label:workbench-zen -label:workspace-edit -label:workspace-symbols -label:workspace-trust -label:zoom -label:inline-chat -label:panel-chat -label:quick-chat -label:tasks -label:error-list -label:winget -label:tree-views -label:freeze-slow-crash-leak -label:engineering -label:cross-file-editing" + "value": "repo:microsoft/vscode assignee:$assignee is:open type:issue -label:\"info-needed\" -label:api -label:api-finalization -label:api-proposal -label:authentication -label:bisect-ext -label:bracket-pair-colorization -label:bracket-pair-guides -label:breadcrumbs -label:callhierarchy -label:chrome-devtools -label:cloud-changes -label:code-lens -label:command-center -label:comments -label:config -label:containers -label:context-keys -label:continue-working-on -label:css-less-scss -label:custom-editors -label:debug -label:debug-disassembly -label:dialogs -label:diff-editor -label:dropdown -label:editor-api -label:editor-autoclosing -label:editor-autoindent -label:editor-bracket-matching -label:editor-clipboard -label:editor-code-actions -label:editor-color-picker -label:editor-columnselect -label:editor-commands -label:editor-comments -label:editor-contrib -label:editor-core -label:editor-drag-and-drop -label:editor-error-widget -label:editor-find -label:editor-folding -label:editor-highlight -label:editor-hover -label:editor-indent-detection -label:editor-indent-guides -label:editor-input -label:editor-input-IME -label:editor-insets -label:editor-minimap -label:editor-multicursor -label:editor-parameter-hints -label:editor-render-whitespace -label:editor-rendering -label:editor-widgets -label:editor-RTL -label:editor-scrollbar -label:editor-sorting -label:editor-sticky-scroll -label:editor-symbols -label:editor-synced-region -label:editor-textbuffer -label:editor-theming -label:editor-wordnav -label:editor-wrapping -label:emmet -label:emmet-parse -label:error-list -label:extension-activation -label:extension-host -label:extension-prerelease -label:extension-recommendations -label:extensions -label:extensions-development -label:file-decorations -label:file-encoding -label:file-explorer -label:file-glob -label:file-io -label:file-nesting -label:file-watcher -label:font-rendering -label:formatting -label:getting-started -label:ghost-text -label:git -label:github -label:github-repositories -label:gpu -label:grammar -label:grid-widget -label:html -label:icon-brand -label:icons-product -label:image-preview -label:inlay-hints -label:inline-completions -label:install-update -label:intellisense-config -label:interactive-playground -label:interactive-window -label:issue-bot -label:issue-reporter -label:javascript -label:json -label:keybindings -label:keybindings-editor -label:keyboard-layout -label:chat -label:l10n-platform -label:label-provider -label:languages-basic -label:languages-diagnostics -label:languages-guessing -label:layout -label:lcd-text-rendering -label:list-widget -label:live-preview -label:log -label:markdown -label:marketplace -label:menus -label:merge-conflict -label:merge-editor -label:merge-editor-workbench -label:monaco-editor -label:native-file-dialog -label:network -label:notebook -label:notebook-accessibility -label:notebook-api -label:notebook-cell-editor -label:notebook-celltoolbar -label:notebook-clipboard -label:notebook-commands -label:notebook-debugging -label:notebook-diff -label:notebook-dnd -label:notebook-execution -label:notebook-find -label:notebook-folding -label:notebook-getting-started -label:notebook-globaltoolbar -label:notebook-ipynb -label:notebook-kernel -label:notebook-kernel-picker -label:notebook-language -label:notebook-layout -label:notebook-markdown -label:notebook-output -label:notebook-perf -label:notebook-remote -label:notebook-serialization -label:notebook-statusbar -label:notebook-toc-outline -label:notebook-undo-redo -label:notebook-variables -label:notebook-workbench-integration -label:notebook-workflow -label:notebook-sticky-scroll -label:notebook-format -label:notebook-code-actions -label:open-editors -label:opener -label:outline -label:output -label:packaging -label:perf -label:perf-bloat -label:perf-startup -label:php -label:portable-mode -label:proxy -label:quick-open -label:quick-pick -label:references-viewlet -label:release-notes -label:remote -label:remote-connection -label:remote-explorer -label:remote-tunnel -label:rename -label:runCommands -label:sandbox -label:sash-widget -label:scm -label:screencast-mode -label:search -label:search-api -label:search-editor -label:search-replace -label:semantic-tokens -label:server -label:settings-editor -label:settings-sync -label:settings-sync-server -label:shared-process -label:simple-file-dialog -label:smart-select -label:snap -label:snippets -label:splitview-widget -label:ssh -label:suggest -label:table-widget -label:tasks -label:telemetry -label:terminal -label:terminal-accessibility -label:terminal-conpty -label:terminal-editors -label:terminal-external -label:terminal-find -label:terminal-input -label:terminal-layout -label:terminal-links -label:terminal-local-echo -label:terminal-persistence -label:terminal-process -label:terminal-profiles -label:terminal-quick-fix -label:terminal-rendering -label:terminal-shell-bash -label:terminal-shell-cmd -label:terminal-shell-fish -label:terminal-shell-git-bash -label:terminal-shell-integration -label:terminal-shell-pwsh -label:terminal-shell-zsh -label:terminal-sticky-scroll -label:terminal-tabs -label:testing -label:themes -label:timeline -label:timeline-git -label:timeline-local-history -label:titlebar -label:tokenization -label:touch/pointer -label:trackpad/scroll -label:tree-views -label:tree-widget -label:typescript -label:undo-redo -label:unicode-highlight -label:uri -label:user-profiles -label:ux -label:variable-resolving -label:VIM -label:virtual-workspaces -label:vscode-website -label:vscode.dev -label:web -label:webview -label:webview-views -label:workbench-actions -label:workbench-banner -label:workbench-cli -label:workbench-diagnostics -label:workbench-dnd -label:workbench-editor-grid -label:workbench-editor-groups -label:workbench-editor-resolver -label:workbench-editors -label:workbench-electron -label:workbench-fonts -label:workbench-history -label:workbench-hot-exit -label:workbench-hover -label:workbench-launch -label:workbench-link -label:workbench-multiroot -label:workbench-notifications -label:workbench-os-integration -label:workbench-rapid-render -label:workbench-run-as-admin -label:workbench-state -label:workbench-status -label:workbench-tabs -label:workbench-touchbar -label:workbench-untitled-editors -label:workbench-views -label:workbench-welcome -label:workbench-window -label:workbench-workspace -label:workbench-zen -label:workspace-edit -label:workspace-symbols -label:workspace-trust -label:zoom -label:inline-chat -label:panel-chat -label:quick-chat -label:tasks -label:error-list -label:winget -label:tree-views -label:freeze-slow-crash-leak -label:engineering -label:cross-file-editing -label:microsoft-authentication -label:github-authentication -label:lm-access -label:secret-storage" }, { "kind": 1, From 7722c2bb0f4b17e1fa4cee6a24d3f0f438348ba9 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 17 Dec 2024 20:27:55 +0100 Subject: [PATCH 059/200] Git - adjust command `when` clauses (#236392) * Add telemetry for troubleshooting * Adjust command when clause --- extensions/git/package.json | 24 ++++++++++++------------ extensions/git/src/commands.ts | 15 ++++++++++++++- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 8b8f52643a896..08349fd478371 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -943,19 +943,19 @@ "command": "git.stageSelectedRanges", "key": "ctrl+k ctrl+alt+s", "mac": "cmd+k cmd+alt+s", - "when": "editorTextFocus && resourceScheme =~ /^git$|^file$/" + "when": "editorTextFocus && resourceScheme == file" }, { "command": "git.unstageSelectedRanges", "key": "ctrl+k ctrl+n", "mac": "cmd+k cmd+n", - "when": "isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "editorTextFocus && isInDiffEditor && isInDiffRightEditor && resourceScheme == git" }, { "command": "git.revertSelectedRanges", "key": "ctrl+k ctrl+r", "mac": "cmd+k cmd+r", - "when": "editorTextFocus && resourceScheme =~ /^git$|^file$/" + "when": "editorTextFocus && resourceScheme == file" } ], "menus": { @@ -1026,7 +1026,7 @@ }, { "command": "git.stageSelectedRanges", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && resourceScheme == file" }, { "command": "git.stageChange", @@ -1034,7 +1034,7 @@ }, { "command": "git.revertSelectedRanges", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && resourceScheme == file" }, { "command": "git.revertChange", @@ -1054,7 +1054,7 @@ }, { "command": "git.unstageSelectedRanges", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && isInDiffRightEditor && resourceScheme == git" }, { "command": "git.clean", @@ -2068,17 +2068,17 @@ { "command": "git.stageSelectedRanges", "group": "2_git@1", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && isInDiffRightEditor && !isEmbeddedDiffEditor && resourceScheme == file" }, { "command": "git.unstageSelectedRanges", "group": "2_git@2", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && isInDiffRightEditor && !isEmbeddedDiffEditor && resourceScheme == git" }, { "command": "git.revertSelectedRanges", "group": "2_git@3", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && isInDiffRightEditor && !isEmbeddedDiffEditor && resourceScheme == file" }, { "command": "git.stashApplyEditor", @@ -2096,17 +2096,17 @@ { "command": "git.stageSelectedRanges", "group": "2_git@1", - "when": "isInDiffRightEditor && !isEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && isInDiffRightEditor && !isEmbeddedDiffEditor && resourceScheme == file" }, { "command": "git.unstageSelectedRanges", "group": "2_git@2", - "when": "isInDiffRightEditor && !isEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && isInDiffRightEditor && !isEmbeddedDiffEditor && resourceScheme == git" }, { "command": "git.revertSelectedRanges", "group": "2_git@3", - "when": "isInDiffRightEditor && !isEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && isInDiffRightEditor && !isEmbeddedDiffEditor && resourceScheme == file" } ], "editor/content": [ diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 64905cd55f36b..8a31393120157 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1563,12 +1563,16 @@ export class CommandCenter { return; } + this.logger.trace(`[CommandCenter][stageSelectedChanges] changes: ${JSON.stringify(changes)}`); + const modifiedDocument = textEditor.document; const selectedLines = toLineRanges(textEditor.selections, modifiedDocument); const selectedChanges = changes .map(change => selectedLines.reduce((result, range) => result || intersectDiffWithRange(modifiedDocument, change, range), null)) .filter(d => !!d) as LineChange[]; + this.logger.trace(`[CommandCenter][stageSelectedChanges] selectedChanges: ${JSON.stringify(selectedChanges)}`); + if (!selectedChanges.length) { window.showInformationMessage(l10n.t('The selection range does not contain any changes.')); return; @@ -1745,6 +1749,8 @@ export class CommandCenter { return; } + this.logger.trace(`[CommandCenter][revertSelectedRanges] changes: ${JSON.stringify(changes)}`); + const modifiedDocument = textEditor.document; const selections = textEditor.selections; const selectedChanges = changes.filter(change => { @@ -1757,6 +1763,8 @@ export class CommandCenter { return; } + this.logger.trace(`[CommandCenter][revertSelectedRanges] selectedChanges: ${JSON.stringify(selectedChanges)}`); + const selectionsBeforeRevert = textEditor.selections; await this._revertChanges(textEditor, selectedChanges); textEditor.selections = selectionsBeforeRevert; @@ -1835,6 +1843,8 @@ export class CommandCenter { return; } + this.logger.trace(`[CommandCenter][unstageSelectedRanges] changes: ${JSON.stringify(changes)}`); + const originalUri = toGitUri(modifiedUri, 'HEAD'); const originalDocument = await workspace.openTextDocument(originalUri); const selectedLines = toLineRanges(textEditor.selections, modifiedDocument); @@ -1848,8 +1858,11 @@ export class CommandCenter { } const invertedDiffs = selectedDiffs.map(invertLineChange); - const result = applyLineChanges(modifiedDocument, originalDocument, invertedDiffs); + this.logger.trace(`[CommandCenter][unstageSelectedRanges] selectedDiffs: ${JSON.stringify(selectedDiffs)}`); + this.logger.trace(`[CommandCenter][unstageSelectedRanges] invertedDiffs: ${JSON.stringify(invertedDiffs)}`); + + const result = applyLineChanges(modifiedDocument, originalDocument, invertedDiffs); await this.runByRepository(modifiedUri, async (repository, resource) => await repository.stage(resource, result)); } From 7cc28c3e81227c60347c0ce507f6ee2283732c38 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Dec 2024 11:30:32 -0800 Subject: [PATCH 060/200] Add markdown validation status item Fixes #236399 --- .../src/languageFeatures/diagnostics.ts | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index f54c136ad1a44..8ea0cac41c124 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -72,10 +72,61 @@ class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider { } } +function registerMarkdownStatusItem(selector: vscode.DocumentSelector, commandManager: CommandManager): vscode.Disposable { + const statusItem = vscode.languages.createLanguageStatusItem('markdownStatus', selector); + + const enabledSettingId = 'validate.enabled'; + const commandId = '_markdown.toggleValidation'; + + const commandSub = commandManager.register({ + id: commandId, + execute: (enabled: boolean) => { + vscode.workspace.getConfiguration('markdown').update(enabledSettingId, enabled); + } + }); + + const update = () => { + const activeDoc = vscode.window.activeTextEditor?.document; + const markdownDoc = activeDoc?.languageId === 'markdown' ? activeDoc : undefined; + + const enabled = vscode.workspace.getConfiguration('markdown', markdownDoc).get(enabledSettingId); + if (enabled) { + statusItem.text = vscode.l10n.t('Link validation enabled'); + statusItem.command = { + command: commandId, + arguments: [false], + title: vscode.l10n.t('Disable'), + tooltip: vscode.l10n.t('Disable validation of Markdown links'), + }; + } else { + statusItem.text = vscode.l10n.t('Link validation disabled'); + statusItem.command = { + command: commandId, + arguments: [true], + title: vscode.l10n.t('Enable'), + tooltip: vscode.l10n.t('Enable validation of Markdown links'), + }; + } + }; + update(); + + return vscode.Disposable.from( + statusItem, + commandSub, + vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('markdown.' + enabledSettingId)) { + update(); + } + }), + ); +} export function registerDiagnosticSupport( selector: vscode.DocumentSelector, commandManager: CommandManager, ): vscode.Disposable { - return AddToIgnoreLinksQuickFixProvider.register(selector, commandManager); + return vscode.Disposable.from( + AddToIgnoreLinksQuickFixProvider.register(selector, commandManager), + registerMarkdownStatusItem(selector, commandManager), + ); } From a47b13ebc2d1b5db4d815ed9cde86278248cfaa7 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Dec 2024 11:59:05 -0800 Subject: [PATCH 061/200] Small cleanup follow up on #236145 - Don't send content as json - Reuse existing load helper --- .../preview-src/index.ts | 21 +++++++++---------- .../preview-src/settings.ts | 8 +++++-- .../src/preview/documentRenderer.ts | 2 +- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index 336245a1fdfd9..f9a0abc4f537e 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -7,7 +7,7 @@ import { ActiveLineMarker } from './activeLineMarker'; import { onceDocumentLoaded } from './events'; import { createPosterForVsCode } from './messaging'; import { getEditorLineNumberForPageOffset, scrollToRevealSourceLine, getLineElementForFragment } from './scroll-sync'; -import { SettingsManager, getData } from './settings'; +import { SettingsManager, getData, getRawData } from './settings'; import throttle = require('lodash.throttle'); import morphdom from 'morphdom'; import type { ToWebviewMessage } from '../types/previewMessaging'; @@ -61,8 +61,16 @@ function doAfterImagesLoaded(cb: () => void) { } onceDocumentLoaded(() => { - const scrollProgress = state.scrollProgress; + // Load initial html + const htmlParser = new DOMParser(); + const markDownHtml = htmlParser.parseFromString( + getRawData('data-initial-md-content'), + 'text/html' + ); + document.body.appendChild(markDownHtml.body); + // Restore + const scrollProgress = state.scrollProgress; addImageContexts(); if (typeof scrollProgress === 'number' && !settings.settings.fragment) { doAfterImagesLoaded(() => { @@ -353,15 +361,6 @@ document.addEventListener('click', event => { } }, true); -window.addEventListener('load', () => { - const htmlParser = new DOMParser(); - const markDownHtml = htmlParser.parseFromString( - decodeURIComponent(getData('data-md-content')), - 'text/html' - ); - document.body.appendChild(markDownHtml.body); -}); - window.addEventListener('scroll', throttle(() => { updateScrollProgress(); diff --git a/extensions/markdown-language-features/preview-src/settings.ts b/extensions/markdown-language-features/preview-src/settings.ts index 1bbe3477f254b..0fb5d0c2686cc 100644 --- a/extensions/markdown-language-features/preview-src/settings.ts +++ b/extensions/markdown-language-features/preview-src/settings.ts @@ -16,18 +16,22 @@ export interface PreviewSettings { readonly webviewResourceRoot: string; } -export function getData(key: string): T { +export function getRawData(key: string): string { const element = document.getElementById('vscode-markdown-preview-data'); if (element) { const data = element.getAttribute(key); if (data) { - return JSON.parse(data); + return data; } } throw new Error(`Could not load data for ${key}`); } +export function getData(key: string): T { + return JSON.parse(getRawData(key)); +} + export class SettingsManager { private _settings: PreviewSettings = getData('data-settings'); diff --git a/extensions/markdown-language-features/src/preview/documentRenderer.ts b/extensions/markdown-language-features/src/preview/documentRenderer.ts index 13e709c765f0c..eeab8e19d9d6b 100644 --- a/extensions/markdown-language-features/src/preview/documentRenderer.ts +++ b/extensions/markdown-language-features/src/preview/documentRenderer.ts @@ -99,7 +99,7 @@ export class MdDocumentRenderer { data-settings="${escapeAttribute(JSON.stringify(initialData))}" data-strings="${escapeAttribute(JSON.stringify(previewStrings))}" data-state="${escapeAttribute(JSON.stringify(state || {}))}" - data-md-content="${escapeAttribute(JSON.stringify(encodeURIComponent(body.html)))}"> + data-initial-md-content="${escapeAttribute(body.html)}"> ${this._getStyles(resourceProvider, sourceUri, config, imageInfo)} From 20dc4d7d2647c3f69a55fd971f66c273666959a3 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 17 Dec 2024 21:37:33 +0100 Subject: [PATCH 062/200] Git - add more tracing to stage/unstage/revert commands (#236409) --- extensions/git/src/blame.ts | 13 ++++----- extensions/git/src/commands.ts | 26 +++++++++++++++++- extensions/git/src/staging.ts | 50 +++++++++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 10 deletions(-) diff --git a/extensions/git/src/blame.ts b/extensions/git/src/blame.ts index 916d906a0a21a..5c65fbcf1fccd 100644 --- a/extensions/git/src/blame.ts +++ b/extensions/git/src/blame.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DecorationOptions, l10n, Position, Range, TextEditor, TextEditorChange, TextEditorDecorationType, TextEditorChangeKind, ThemeColor, Uri, window, workspace, EventEmitter, ConfigurationChangeEvent, StatusBarItem, StatusBarAlignment, Command, MarkdownString, TextEditorDiffInformation } from 'vscode'; +import { DecorationOptions, l10n, Position, Range, TextEditor, TextEditorChange, TextEditorDecorationType, TextEditorChangeKind, ThemeColor, Uri, window, workspace, EventEmitter, ConfigurationChangeEvent, StatusBarItem, StatusBarAlignment, Command, MarkdownString } from 'vscode'; import { Model } from './model'; import { dispose, fromNow, IDisposable } from './util'; import { Repository } from './repository'; @@ -11,6 +11,7 @@ import { throttle } from './decorators'; import { BlameInformation } from './git'; import { fromGitUri, isGitUri } from './uri'; import { emojify, ensureEmojis } from './emoji'; +import { getWorkingTreeAndIndexDiffInformation, getWorkingTreeDiffInformation } from './staging'; function lineRangesContainLine(changes: readonly TextEditorChange[], lineNumber: number): boolean { return changes.some(c => c.modified.startLineNumber <= lineNumber && lineNumber < c.modified.endLineNumberExclusive); @@ -277,10 +278,6 @@ export class GitBlameController { return blameInformation; } - private _findDiffInformation(textEditor: TextEditor, ref: string): TextEditorDiffInformation | undefined { - return textEditor.diffInformation?.find(diff => diff.original && isGitUri(diff.original) && fromGitUri(diff.original).ref === ref); - } - @throttle private async _updateTextEditorBlameInformation(textEditor: TextEditor | undefined, showBlameInformationForPositionZero = false): Promise { if (!textEditor?.diffInformation || textEditor !== window.activeTextEditor) { @@ -319,7 +316,7 @@ export class GitBlameController { workingTreeAndIndexChanges = undefined; } else if (ref === '') { // Resource on the right-hand side of the diff editor when viewing a resource from the index. - const diffInformationWorkingTreeAndIndex = this._findDiffInformation(textEditor, 'HEAD'); + const diffInformationWorkingTreeAndIndex = getWorkingTreeAndIndexDiffInformation(textEditor); // Working tree + index diff information is present and it is stale if (diffInformationWorkingTreeAndIndex && diffInformationWorkingTreeAndIndex.isStale) { @@ -333,7 +330,7 @@ export class GitBlameController { } } else { // Working tree diff information. Diff Editor (Working Tree) -> Text Editor - const diffInformationWorkingTree = this._findDiffInformation(textEditor, '~') ?? this._findDiffInformation(textEditor, ''); + const diffInformationWorkingTree = getWorkingTreeDiffInformation(textEditor); // Working tree diff information is not present or it is stale if (!diffInformationWorkingTree || diffInformationWorkingTree.isStale) { @@ -341,7 +338,7 @@ export class GitBlameController { } // Working tree + index diff information - const diffInformationWorkingTreeAndIndex = this._findDiffInformation(textEditor, 'HEAD'); + const diffInformationWorkingTreeAndIndex = getWorkingTreeAndIndexDiffInformation(textEditor); // Working tree + index diff information is present and it is stale if (diffInformationWorkingTreeAndIndex && diffInformationWorkingTreeAndIndex.isStale) { diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 8a31393120157..dde1c99049a87 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -12,7 +12,7 @@ import { ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, Remo import { Git, Stash } from './git'; import { Model } from './model'; import { GitResourceGroup, Repository, Resource, ResourceGroupType } from './repository'; -import { DiffEditorSelectionHunkToolbarContext, applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineChange, toLineRanges } from './staging'; +import { DiffEditorSelectionHunkToolbarContext, applyLineChanges, getModifiedRange, getWorkingTreeAndIndexDiffInformation, getWorkingTreeDiffInformation, intersectDiffWithRange, invertLineChange, toLineChanges, toLineRanges } from './staging'; import { fromGitUri, toGitUri, isGitUri, toMergeUris, toMultiFileDiffEditorUris } from './uri'; import { dispose, grep, isDefined, isDescendant, pathEquals, relativePath, truncate } from './util'; import { GitTimelineItem } from './timelineProvider'; @@ -1565,6 +1565,12 @@ export class CommandCenter { this.logger.trace(`[CommandCenter][stageSelectedChanges] changes: ${JSON.stringify(changes)}`); + const workingTreeDiffInformation = getWorkingTreeDiffInformation(textEditor); + if (workingTreeDiffInformation) { + this.logger.trace(`[CommandCenter][stageSelectedChanges] diffInformation: ${JSON.stringify(workingTreeDiffInformation)}`); + this.logger.trace(`[CommandCenter][stageSelectedChanges] diffInformation changes: ${JSON.stringify(toLineChanges(workingTreeDiffInformation))}`); + } + const modifiedDocument = textEditor.document; const selectedLines = toLineRanges(textEditor.selections, modifiedDocument); const selectedChanges = changes @@ -1751,6 +1757,12 @@ export class CommandCenter { this.logger.trace(`[CommandCenter][revertSelectedRanges] changes: ${JSON.stringify(changes)}`); + const workingTreeDiffInformation = getWorkingTreeDiffInformation(textEditor); + if (workingTreeDiffInformation) { + this.logger.trace(`[CommandCenter][revertSelectedRanges] diffInformation: ${JSON.stringify(workingTreeDiffInformation)}`); + this.logger.trace(`[CommandCenter][revertSelectedRanges] diffInformation changes: ${JSON.stringify(toLineChanges(workingTreeDiffInformation))}`); + } + const modifiedDocument = textEditor.document; const selections = textEditor.selections; const selectedChanges = changes.filter(change => { @@ -1845,6 +1857,18 @@ export class CommandCenter { this.logger.trace(`[CommandCenter][unstageSelectedRanges] changes: ${JSON.stringify(changes)}`); + const workingTreeDiffInformation = getWorkingTreeDiffInformation(textEditor); + if (workingTreeDiffInformation) { + this.logger.trace(`[CommandCenter][unstageSelectedRanges] diffInformation (working tree): ${JSON.stringify(workingTreeDiffInformation)}`); + this.logger.trace(`[CommandCenter][unstageSelectedRanges] diffInformation changes (working tree): ${JSON.stringify(toLineChanges(workingTreeDiffInformation))}`); + } + + const workingTreeAndIndexDiffInformation = getWorkingTreeAndIndexDiffInformation(textEditor); + if (workingTreeAndIndexDiffInformation) { + this.logger.trace(`[CommandCenter][unstageSelectedRanges] diffInformation (working tree + index): ${JSON.stringify(workingTreeAndIndexDiffInformation)}`); + this.logger.trace(`[CommandCenter][unstageSelectedRanges] diffInformation changes (working tree + index): ${JSON.stringify(toLineChanges(workingTreeAndIndexDiffInformation))}`); + } + const originalUri = toGitUri(modifiedUri, 'HEAD'); const originalDocument = await workspace.openTextDocument(originalUri); const selectedLines = toLineRanges(textEditor.selections, modifiedDocument); diff --git a/extensions/git/src/staging.ts b/extensions/git/src/staging.ts index 2dcc6d54487a7..38a462aedf62f 100644 --- a/extensions/git/src/staging.ts +++ b/extensions/git/src/staging.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { TextDocument, Range, LineChange, Selection, Uri } from 'vscode'; +import { TextDocument, Range, LineChange, Selection, Uri, TextEditor, TextEditorDiffInformation } from 'vscode'; +import { fromGitUri, isGitUri } from './uri'; export function applyLineChanges(original: TextDocument, modified: TextDocument, diffs: LineChange[]): string { const result: string[] = []; @@ -143,6 +144,53 @@ export function invertLineChange(diff: LineChange): LineChange { }; } +export function toLineChanges(diffInformation: TextEditorDiffInformation): LineChange[] { + return diffInformation.changes.map(x => { + let originalStartLineNumber: number; + let originalEndLineNumber: number; + let modifiedStartLineNumber: number; + let modifiedEndLineNumber: number; + + if (x.original.startLineNumber === x.original.endLineNumberExclusive) { + // Insertion + originalStartLineNumber = x.original.startLineNumber - 1; + originalEndLineNumber = 0; + } else { + originalStartLineNumber = x.original.startLineNumber; + originalEndLineNumber = x.original.endLineNumberExclusive - 1; + } + + if (x.modified.startLineNumber === x.modified.endLineNumberExclusive) { + // Deletion + modifiedStartLineNumber = x.modified.startLineNumber - 1; + modifiedEndLineNumber = 0; + } else { + modifiedStartLineNumber = x.modified.startLineNumber; + modifiedEndLineNumber = x.modified.endLineNumberExclusive - 1; + } + + return { + originalStartLineNumber, + originalEndLineNumber, + modifiedStartLineNumber, + modifiedEndLineNumber + }; + }); +} + +export function getWorkingTreeDiffInformation(textEditor: TextEditor): TextEditorDiffInformation | undefined { + // Working tree diff information. Diff Editor (Working Tree) -> Text Editor + return getDiffInformation(textEditor, '~') ?? getDiffInformation(textEditor, ''); +} + +export function getWorkingTreeAndIndexDiffInformation(textEditor: TextEditor): TextEditorDiffInformation | undefined { + return getDiffInformation(textEditor, 'HEAD'); +} + +function getDiffInformation(textEditor: TextEditor, ref: string): TextEditorDiffInformation | undefined { + return textEditor.diffInformation?.find(diff => diff.original && isGitUri(diff.original) && fromGitUri(diff.original).ref === ref); +} + export interface DiffEditorSelectionHunkToolbarContext { mapping: unknown; /** From 999f28e49fee2e38050fd1a9c6ab37aa76841d6a Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Tue, 17 Dec 2024 12:39:39 -0800 Subject: [PATCH 063/200] Extend hover styling for entire sticky line (#236410) extend hover styling for entire sticky line --- .../browser/media/notebookEditorStickyScroll.css | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css b/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css index 25fa90546d698..a723d49cfe0d4 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css @@ -38,10 +38,8 @@ .monaco-workbench .notebookOverlay .notebook-sticky-scroll-container - .notebook-sticky-scroll-element - .notebook-sticky-scroll-header:hover { + .notebook-sticky-scroll-element:hover { background-color: var(--vscode-editorStickyScrollHover-background); - width: 100%; cursor: pointer; } @@ -55,13 +53,11 @@ .monaco-workbench.hc-light .notebookOverlay .notebook-sticky-scroll-container - .notebook-sticky-scroll-element - .notebook-sticky-scroll-header:hover, + .notebook-sticky-scroll-element:hover, .monaco-workbench.hc-black .notebookOverlay .notebook-sticky-scroll-container - .notebook-sticky-scroll-element - .notebook-sticky-scroll-header:hover { + .notebook-sticky-scroll-element:hover { outline: 1px dashed var(--vscode-contrastActiveBorder); outline-offset: -2px; } From b0e6e13be600c35f7444f16b4c5f59eb503ec2f1 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Tue, 17 Dec 2024 13:34:38 -0800 Subject: [PATCH 064/200] notebook find replace position vs global toolbar (#236407) * notebook find replace position vs global toolbar * use runAndSubscribe --- .../contrib/find/notebookFindReplaceWidget.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts index 6e8651a4a32d3..8509900d4b07b 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts @@ -20,6 +20,7 @@ import { Widget } from '../../../../../../base/browser/ui/widget.js'; import { Action, ActionRunner, IAction, IActionRunner, Separator } from '../../../../../../base/common/actions.js'; import { Delayer } from '../../../../../../base/common/async.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; +import { Event } from '../../../../../../base/common/event.js'; import { KeyCode } from '../../../../../../base/common/keyCodes.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; import { isSafari } from '../../../../../../base/common/platform.js'; @@ -345,6 +346,17 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._domNode = document.createElement('div'); this._domNode.classList.add('simple-fr-find-part-wrapper'); + + this._register(Event.runAndSubscribe(this._configurationService.onDidChangeConfiguration, e => { + if (!e || e.affectsConfiguration(NotebookSetting.globalToolbar)) { + if (this._notebookEditor.notebookOptions.getLayoutConfiguration().globalToolbar) { + this._domNode.style.top = '26px'; + } else { + this._domNode.style.top = '0px'; + } + } + })); + this._register(this._state.onFindReplaceStateChange((e) => this._onStateChanged(e))); this._scopedContextKeyService = contextKeyService.createScoped(this._domNode); From cece885ae0d18ae017007ccdd7fb808fa23d6b1e Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 17 Dec 2024 14:59:26 -0800 Subject: [PATCH 065/200] testing: show item when coverage is filtered, standardize some tree classes (#236418) Fixes #235147 --- src/vs/platform/actions/common/actions.ts | 1 + .../contrib/testing/browser/media/testing.css | 78 ++++++------ .../testing/browser/testCoverageView.ts | 113 +++++++++++++++--- .../testResultsView/testResultsTree.ts | 8 +- .../testResultsView/testResultsViewContent.ts | 2 +- .../testing/browser/testingExplorerView.ts | 4 +- 6 files changed, 137 insertions(+), 69 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 7f00003c0f305..2b784b5bf5e1e 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -139,6 +139,7 @@ export class MenuId { static readonly TestPeekElement = new MenuId('TestPeekElement'); static readonly TestPeekTitle = new MenuId('TestPeekTitle'); static readonly TestCallStack = new MenuId('TestCallStack'); + static readonly TestCoverageFilterItem = new MenuId('TestCoverageFilterItem'); static readonly TouchBarContext = new MenuId('TouchBarContext'); static readonly TitleBarContext = new MenuId('TitleBarContext'); static readonly TitleBarTitleContext = new MenuId('TitleBarTitleContext'); diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index d56ac88a18513..ef2437bd58894 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -43,34 +43,41 @@ position: relative; } -.test-explorer .test-item .label, -.test-output-peek-tree .test-peek-item .name, -.test-coverage-list-item .name, -.test-coverage-list-item-label { - flex-grow: 1; - width: 0; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; +.testing-stdtree-container { + display: flex; + align-items: center; + + .label { + flex-grow: 1; + width: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; - .monaco-list.horizontal-scrolling & { - width: auto; - overflow: visible; + .codicon { + vertical-align: middle; + font-size: 1em; + transform: scale(1.25); + margin: 0 0.125em; + } + + .monaco-list.horizontal-scrolling & { + width: auto; + overflow: visible; + } } -} -.test-output-peek-tree .test-peek-item .name .codicon, -.test-explorer .test-item .label .codicon { - vertical-align: middle; - font-size: 1em; - transform: scale(1.25); - margin: 0 0.125em; -} + .monaco-action-bar { + display: none; + flex-shrink: 0; + margin-right: 0.8em; + } -.test-explorer .test-item, -.test-output-peek-tree .test-peek-item { - display: flex; - align-items: center; + &:hover, &.focused { + .monaco-action-bar { + display: initial; + } + } } .test-output-peek-tree { @@ -78,14 +85,16 @@ border-left: 1px solid var(--vscode-panelSection-border); } -.test-output-peek-tree .monaco-list-row .monaco-action-bar, -.test-explorer .monaco-list-row .monaco-action-bar, .test-explorer .monaco-list-row .codicon-testing-hidden { display: none; flex-shrink: 0; margin-right: 0.8em; } +.test-explorer .monaco-list-row .monaco-action-bar.testing-is-continuous-run { + display: initial; +} + .test-explorer .monaco-list-row .monaco-action-bar .codicon-testing-continuous-is-on { color: var(--vscode-inputOption-activeForeground); border-color: var(--vscode-inputOption-activeBorder); @@ -102,14 +111,6 @@ display: block !important; } -.test-explorer .monaco-list-row:hover .monaco-action-bar, -.test-explorer .monaco-list-row.focused .monaco-action-bar, -.test-explorer .monaco-list-row .monaco-action-bar.testing-is-continuous-run, -.test-output-peek-tree .monaco-list-row:hover .monaco-action-bar, -.test-output-peek-tree .monaco-list-row.focused .monaco-action-bar { - display: initial; -} - .test-explorer .monaco-list-row .test-is-hidden .codicon-testing-hidden { display: block; margin-right: 9px; @@ -467,15 +468,6 @@ /** -- coverage */ -.coverage-view-is-filtered > .pane-header > .actions { - display: block !important; -} - -.test-coverage-list-item { - display: flex; - align-items: center; -} - .test-coverage-bars { display: flex; align-items: center; diff --git a/src/vs/workbench/contrib/testing/browser/testCoverageView.ts b/src/vs/workbench/contrib/testing/browser/testCoverageView.ts index 5ee19fb10f6a8..b660cc798b1cc 100644 --- a/src/vs/workbench/contrib/testing/browser/testCoverageView.ts +++ b/src/vs/workbench/contrib/testing/browser/testCoverageView.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../base/browser/dom.js'; +import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.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'; @@ -24,7 +25,9 @@ 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 { getActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { Action2, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { ICommandService } 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 } from '../../../../platform/contextview/browser/contextView.js'; @@ -42,17 +45,17 @@ 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 { 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, 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'; +import { CoverageDetails, DetailType, ICoverageCount, IDeclarationCoverage, ITestItem, TestResultState } from '../common/testTypes.js'; +import * as coverUtils from './codeCoverageDisplayUtils.js'; +import { testingStatesToIcons, testingWasCovered } from './icons.js'; +import { CoverageBarSource, ManagedTestCoverageBars } from './testCoverageBars.js'; const enum CoverageSortOrder { Coverage, @@ -95,13 +98,6 @@ export class TestCoverageView extends ViewPane { 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 { @@ -197,6 +193,16 @@ class RevealUncoveredDeclarations { constructor(public readonly n: number) { } } +class CurrentlyFilteredTo { + public readonly id = String(fnNodeId++); + + public get label() { + return localize('filteredToTest', "Showing coverage for \"{0}\"", this.testItem.label); + } + + constructor(public readonly testItem: ITestItem) { } +} + class LoadingDetails { public readonly id = String(fnNodeId++); public readonly label = localize('loadingCoverageDetails', "Loading Coverage Details..."); @@ -204,7 +210,7 @@ class LoadingDetails { /** Type of nodes returned from {@link TestCoverage}. Note: value is *always* defined. */ type TestCoverageFileNode = IPrefixTreeNode; -type CoverageTreeElement = TestCoverageFileNode | DeclarationCoverageNode | LoadingDetails | RevealUncoveredDeclarations; +type CoverageTreeElement = TestCoverageFileNode | DeclarationCoverageNode | LoadingDetails | RevealUncoveredDeclarations | CurrentlyFilteredTo; const isFileCoverage = (c: CoverageTreeElement): c is TestCoverageFileNode => typeof c === 'object' && 'value' in c; const isDeclarationCoverage = (c: CoverageTreeElement): c is DeclarationCoverageNode => c instanceof DeclarationCoverageNode; @@ -221,9 +227,12 @@ class TestCoverageTree extends Disposable { sortOrder: IObservable, @IInstantiationService instantiationService: IInstantiationService, @IEditorService editorService: IEditorService, + @ICommandService commandService: ICommandService, ) { super(); + container.classList.add('testing-stdtree'); + this.tree = instantiationService.createInstance( WorkbenchCompressibleObjectTree, 'TestCoverageView', @@ -233,6 +242,7 @@ class TestCoverageTree extends Disposable { instantiationService.createInstance(FileCoverageRenderer, labels), instantiationService.createInstance(DeclarationCoverageRenderer), instantiationService.createInstance(BasicRenderer), + instantiationService.createInstance(CurrentlyFilteredToRenderer), ], { expandOnlyOnTwistieClick: true, @@ -289,6 +299,9 @@ class TestCoverageTree extends Disposable { } else if (isDeclarationCoverage(e.element)) { resource = e.element.uri; selection = e.element.location; + } else if (e.element instanceof CurrentlyFilteredTo) { + commandService.executeCommand(TestCommandId.CoverageFilterToTest); + return; } } if (!resource) { @@ -351,7 +364,19 @@ class TestCoverageTree extends Disposable { } })); - this.tree.setChildren(null, Iterable.map(files, toChild)); + let children = Iterable.map(files, toChild); + const filteredTo = showOnlyTest && coverage.result.getTestById(showOnlyTest.toString()); + if (filteredTo) { + children = Iterable.concat( + Iterable.single>({ + element: new CurrentlyFilteredTo(filteredTo), + incompressible: true, + }), + children, + ); + } + + this.tree.setChildren(null, children); } public layout(height: number, width: number) { @@ -409,6 +434,9 @@ class TestCoverageTreeListDelegate implements IListVirtualDelegate { } } +interface IFilteredToTemplate { + label: HTMLElement; + actions: ActionBar; +} + +class CurrentlyFilteredToRenderer implements ICompressibleTreeRenderer { + public static readonly ID = 'C'; + public readonly templateId = CurrentlyFilteredToRenderer.ID; + + constructor( + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + ) { } + + renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: IFilteredToTemplate, height: number | undefined): void { + this.renderInner(node.element.elements[node.element.elements.length - 1] as CurrentlyFilteredTo, templateData); + } + + renderTemplate(container: HTMLElement): IFilteredToTemplate { + container.classList.add('testing-stdtree-container'); + const label = dom.append(container, dom.$('.label')); + const menu = this.menuService.getMenuActions(MenuId.TestCoverageFilterItem, this.contextKeyService, { + shouldForwardArgs: true, + }); + + const actions = new ActionBar(container); + actions.push(getActionBarActions(menu, 'inline').primary, { icon: true, label: false }); + actions.domNode.style.display = 'block'; + + return { label, actions }; + } + + renderElement(element: ITreeNode, index: number, templateData: IFilteredToTemplate, height: number | undefined): void { + this.renderInner(element.element as CurrentlyFilteredTo, templateData); + } + + disposeTemplate(templateData: IFilteredToTemplate): void { + templateData.actions.dispose(); + } + + private renderInner(element: CurrentlyFilteredTo, container: IFilteredToTemplate) { + container.label.innerText = element.label; + } +} + interface FileTemplateData { container: HTMLElement; bars: ManagedTestCoverageBars; @@ -469,7 +542,7 @@ class FileCoverageRenderer implements ICompressibleTreeRenderer action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }) diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts index f93d2065f0c40..421afad78f203 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts @@ -248,7 +248,7 @@ export class TestResultsViewContent extends Disposable { this.contextKeyTestMessage = TestingContextKeys.testMessageContext.bindTo(this.messageContextKeyService); this.contextKeyResultOutdated = TestingContextKeys.testResultOutdated.bindTo(this.messageContextKeyService); - const treeContainer = dom.append(containerElement, dom.$('.test-output-peek-tree')); + const treeContainer = dom.append(containerElement, dom.$('.test-output-peek-tree.testing-stdtree')); const tree = this._register(this.instantiationService.createInstance( OutputPeekTree, treeContainer, diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 2febdc0a89c8e..5e84db11b6af9 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -1526,8 +1526,8 @@ class TestItemRenderer extends Disposable /** * @inheritdoc */ - public renderTemplate(container: HTMLElement): ITestElementTemplateData { - const wrapper = dom.append(container, dom.$('.test-item')); + public renderTemplate(wrapper: HTMLElement): ITestElementTemplateData { + wrapper.classList.add('testing-stdtree-container'); const icon = dom.append(wrapper, dom.$('.computed-state')); const label = dom.append(wrapper, dom.$('.label')); From 2a4ed8474889817be7266f7e675892fbb055195d Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 18 Dec 2024 00:39:13 +0100 Subject: [PATCH 066/200] Ensure settings and keybindings views open at correct location (#236412) * make sure settings and keybindings views open at correct location * fix tests --- .../browser/preferences.contribution.ts | 54 ++++++++++++------- .../preferences/browser/settingsEditor2.ts | 2 +- .../preferences/browser/preferencesService.ts | 41 ++++++++------ .../preferences/common/preferences.ts | 7 ++- .../test/browser/preferencesService.test.ts | 38 ++++++------- 5 files changed, 87 insertions(+), 55 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 175e8bd518b00..2a6357e15d456 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -47,6 +47,9 @@ import { IUserDataProfileService, CURRENT_PROFILE_CONTEXT } from '../../../servi import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js'; import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; +import { resolveCommandsContext } from '../../../browser/parts/editor/editorCommandsContext.js'; +import { IEditorGroup, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; +import { IListService } from '../../../../platform/list/browser/listService.js'; const SETTINGS_EDITOR_COMMAND_SEARCH = 'settings.action.search'; @@ -791,9 +794,10 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon ] }); } - run(accessor: ServicesAccessor, args: string | undefined) { - const query = typeof args === 'string' ? args : undefined; - return accessor.get(IPreferencesService).openGlobalKeybindingSettings(false, { query }); + run(accessor: ServicesAccessor, ...args: unknown[]) { + const query = typeof args[0] === 'string' ? args[0] : undefined; + const groupId = getEditorGroupFromArguments(accessor, args)?.id; + return accessor.get(IPreferencesService).openGlobalKeybindingSettings(false, { query, groupId }); } })); this._register(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { @@ -834,8 +838,9 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon ] }); } - run(accessor: ServicesAccessor) { - return accessor.get(IPreferencesService).openGlobalKeybindingSettings(true); + run(accessor: ServicesAccessor, ...args: unknown[]) { + const groupId = getEditorGroupFromArguments(accessor, args)?.id; + return accessor.get(IPreferencesService).openGlobalKeybindingSettings(true, { groupId }); } })); this._register(registerAction2(class extends Action2 { @@ -852,8 +857,9 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon ] }); } - run(accessor: ServicesAccessor) { - const editorPane = accessor.get(IEditorService).activeEditorPane; + run(accessor: ServicesAccessor, ...args: unknown[]) { + const group = getEditorGroupFromArguments(accessor, args); + const editorPane = group?.activeEditorPane; if (editorPane instanceof KeybindingsEditor) { editorPane.search('@source:system'); } @@ -873,8 +879,9 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon ] }); } - run(accessor: ServicesAccessor) { - const editorPane = accessor.get(IEditorService).activeEditorPane; + run(accessor: ServicesAccessor, ...args: unknown[]) { + const group = getEditorGroupFromArguments(accessor, args); + const editorPane = group?.activeEditorPane; if (editorPane instanceof KeybindingsEditor) { editorPane.search('@source:extension'); } @@ -894,8 +901,9 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon ] }); } - run(accessor: ServicesAccessor) { - const editorPane = accessor.get(IEditorService).activeEditorPane; + run(accessor: ServicesAccessor, args: unknown[]) { + const group = getEditorGroupFromArguments(accessor, args); + const editorPane = group?.activeEditorPane; if (editorPane instanceof KeybindingsEditor) { editorPane.search('@source:user'); } @@ -1207,11 +1215,12 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon for (const folder of this.workspaceContextService.getWorkspace().folders) { const commandId = `_workbench.openFolderSettings.${folder.uri.toString()}`; if (!CommandsRegistry.getCommand(commandId)) { - CommandsRegistry.registerCommand(commandId, () => { + CommandsRegistry.registerCommand(commandId, (accessor: ServicesAccessor, ...args: any[]) => { + const groupId = getEditorGroupFromArguments(accessor, args)?.id; if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.FOLDER) { - return this.preferencesService.openWorkspaceSettings({ jsonEditor: false }); + return this.preferencesService.openWorkspaceSettings({ jsonEditor: false, groupId }); } else { - return this.preferencesService.openFolderSettings({ folderUri: folder.uri, jsonEditor: false }); + return this.preferencesService.openFolderSettings({ folderUri: folder.uri, jsonEditor: false, groupId }); } }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { @@ -1261,9 +1270,10 @@ class SettingsEditorTitleContribution extends Disposable implements IWorkbenchCo }] }); } - run(accessor: ServicesAccessor, args: IOpenSettingsActionOptions) { - args = sanitizeOpenSettingsArgs(args); - return accessor.get(IPreferencesService).openUserSettings({ jsonEditor: false, ...args }); + run(accessor: ServicesAccessor, ...args: unknown[]) { + const sanatizedArgs = sanitizeOpenSettingsArgs(args[0]); + const groupId = getEditorGroupFromArguments(accessor, args)?.id; + return accessor.get(IPreferencesService).openUserSettings({ jsonEditor: false, ...sanatizedArgs, groupId }); } }); }; @@ -1289,8 +1299,9 @@ class SettingsEditorTitleContribution extends Disposable implements IWorkbenchCo }] }); } - run(accessor: ServicesAccessor) { - const editorPane = accessor.get(IEditorService).activeEditorPane; + run(accessor: ServicesAccessor, ...args: unknown[]) { + const group = getEditorGroupFromArguments(accessor, args); + const editorPane = group?.activeEditorPane; if (editorPane instanceof SettingsEditor2) { return editorPane.switchToSettingsFile(); } @@ -1300,6 +1311,11 @@ class SettingsEditorTitleContribution extends Disposable implements IWorkbenchCo } } +function getEditorGroupFromArguments(accessor: ServicesAccessor, args: unknown[]): IEditorGroup | undefined { + const context = resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService)); + return context.groupedEditors[0]?.group; +} + const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); registerWorkbenchContribution2(PreferencesActionsContribution.ID, PreferencesActionsContribution, WorkbenchPhase.BlockStartup); registerWorkbenchContribution2(PreferencesContribution.ID, PreferencesContribution, WorkbenchPhase.BlockStartup); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index c1d1c302db171..8e3388a7008e2 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -789,7 +789,7 @@ export class SettingsEditor2 extends EditorPane { private async openSettingsFile(options?: ISettingsEditorOptions): Promise { const currentSettingsTarget = this.settingsTargetsWidget.settingsTarget; - const openOptions: IOpenSettingsOptions = { jsonEditor: true, ...options }; + const openOptions: IOpenSettingsOptions = { jsonEditor: true, groupId: this.group.id, ...options }; if (currentSettingsTarget === ConfigurationTarget.USER_LOCAL) { if (options?.revealSetting) { const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 025cc02e964a4..f0bb2cb9f25b3 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -29,10 +29,10 @@ import { DEFAULT_EDITOR_ASSOCIATION, IEditorPane } from '../../../common/editor. import { EditorInput } from '../../../common/editor/editorInput.js'; import { SideBySideEditorInput } from '../../../common/editor/sideBySideEditorInput.js'; import { IJSONEditingService } from '../../configuration/common/jsonEditing.js'; -import { GroupDirection, IEditorGroupsService } from '../../editor/common/editorGroupsService.js'; -import { IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE } from '../../editor/common/editorService.js'; +import { GroupDirection, IEditorGroup, IEditorGroupsService } from '../../editor/common/editorGroupsService.js'; +import { IEditorService, SIDE_GROUP } from '../../editor/common/editorService.js'; import { KeybindingsEditorInput } from './keybindingsEditorInput.js'; -import { DEFAULT_SETTINGS_EDITOR_SETTING, FOLDER_SETTINGS_PATH, IKeybindingsEditorOptions, IKeybindingsEditorPane, IOpenSettingsOptions, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorOptions, ISettingsGroup, SETTINGS_AUTHORITY, USE_SPLIT_JSON_SETTING, validateSettingsEditorOptions } from '../common/preferences.js'; +import { DEFAULT_SETTINGS_EDITOR_SETTING, FOLDER_SETTINGS_PATH, IKeybindingsEditorPane, IOpenKeybindingsEditorOptions, IOpenSettingsOptions, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorOptions, ISettingsGroup, SETTINGS_AUTHORITY, USE_SPLIT_JSON_SETTING, validateSettingsEditorOptions } from '../common/preferences.js'; import { SettingsEditor2Input } from '../common/preferencesEditorInput.js'; import { defaultKeybindingsContents, DefaultKeybindingsEditorModel, DefaultRawSettingsEditorModel, DefaultSettings, DefaultSettingsEditorModel, Settings2EditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from '../common/preferencesModels.js'; import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js'; @@ -48,6 +48,7 @@ import { IURLService } from '../../../../platform/url/common/url.js'; import { compareIgnoreCase } from '../../../../base/common/strings.js'; import { IExtensionService } from '../../extensions/common/extensions.js'; import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; +import { findGroup } from '../../editor/common/editorGroupFinder.js'; const emptyEditableSettingsContent = '{\n}'; @@ -248,8 +249,8 @@ export class PreferencesService extends Disposable implements IPreferencesServic ...options, focusSearch: true }; - await this.editorService.openEditor(input, validateSettingsEditorOptions(options), options.openToSide ? SIDE_GROUP : undefined); - return this.editorGroupService.activeGroup.activeEditorPane!; + const group = await this.getEditorGroupFromOptions(options); + return (await group.openEditor(input, validateSettingsEditorOptions(options)))!; } openApplicationSettings(options: IOpenSettingsOptions = {}): Promise { @@ -312,7 +313,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.open(folderSettingsUri, options); } - async openGlobalKeybindingSettings(textual: boolean, options?: IKeybindingsEditorOptions): Promise { + async openGlobalKeybindingSettings(textual: boolean, options?: IOpenKeybindingsEditorOptions): Promise { options = { pinned: true, revealIfOpened: true, ...options }; if (textual) { const emptyContents = '// ' + nls.localize('emptyKeybindingsHeader', "Place your key bindings in this file to override the defaults") + '\n[\n]'; @@ -322,18 +323,18 @@ export class PreferencesService extends Disposable implements IPreferencesServic // Create as needed and open in editor await this.createIfNotExists(editableKeybindings, emptyContents); if (openDefaultKeybindings) { - const activeEditorGroup = this.editorGroupService.activeGroup; - const sideEditorGroup = this.editorGroupService.addGroup(activeEditorGroup.id, GroupDirection.RIGHT); + const sourceGroupId = options.groupId ?? this.editorGroupService.activeGroup.id; + const sideEditorGroup = this.editorGroupService.addGroup(sourceGroupId, GroupDirection.RIGHT); await Promise.all([ - this.editorService.openEditor({ resource: this.defaultKeybindingsResource, options: { pinned: true, preserveFocus: true, revealIfOpened: true, override: DEFAULT_EDITOR_ASSOCIATION.id }, label: nls.localize('defaultKeybindings', "Default Keybindings"), description: '' }), + this.editorService.openEditor({ resource: this.defaultKeybindingsResource, options: { pinned: true, preserveFocus: true, revealIfOpened: true, override: DEFAULT_EDITOR_ASSOCIATION.id }, label: nls.localize('defaultKeybindings', "Default Keybindings"), description: '' }, sourceGroupId), this.editorService.openEditor({ resource: editableKeybindings, options }, sideEditorGroup.id) ]); } else { - await this.editorService.openEditor({ resource: editableKeybindings, options }); + await this.editorService.openEditor({ resource: editableKeybindings, options }, options.groupId); } } else { - const editor = (await this.editorService.openEditor(this.instantiationService.createInstance(KeybindingsEditorInput), { ...options })) as IKeybindingsEditorPane; + const editor = (await this.editorService.openEditor(this.instantiationService.createInstance(KeybindingsEditorInput), { ...options }, options.groupId)) as IKeybindingsEditorPane; if (options.query) { editor.search(options.query); } @@ -345,8 +346,16 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.editorService.openEditor({ resource: this.defaultKeybindingsResource, label: nls.localize('defaultKeybindings', "Default Keybindings") }); } + private async getEditorGroupFromOptions(options: IOpenSettingsOptions): Promise { + let group = options?.groupId !== undefined ? this.editorGroupService.getGroup(options.groupId) ?? this.editorGroupService.activeGroup : this.editorGroupService.activeGroup; + if (options.openToSide) { + group = (await this.instantiationService.invokeFunction(findGroup, {}, SIDE_GROUP))[0]; + } + return group; + } + private async openSettingsJson(resource: URI, options: IOpenSettingsOptions): Promise { - const group = options?.openToSide ? SIDE_GROUP : undefined; + const group = await this.getEditorGroupFromOptions(options); const editor = await this.doOpenSettingsJson(resource, options, group); if (editor && options?.revealSetting) { await this.revealSetting(options.revealSetting.key, !!options.revealSetting.edit, editor, resource); @@ -354,7 +363,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return editor; } - private async doOpenSettingsJson(resource: URI, options: ISettingsEditorOptions, group?: SIDE_GROUP_TYPE): Promise { + private async doOpenSettingsJson(resource: URI, options: ISettingsEditorOptions, group: IEditorGroup): Promise { const openSplitJSON = !!this.configurationService.getValue(USE_SPLIT_JSON_SETTING); const openDefaultSettings = !!this.configurationService.getValue(DEFAULT_SETTINGS_EDITOR_SETTING); if (openSplitJSON || openDefaultSettings) { @@ -364,15 +373,15 @@ export class PreferencesService extends Disposable implements IPreferencesServic const configurationTarget = options?.target ?? ConfigurationTarget.USER; const editableSettingsEditorInput = await this.getOrCreateEditableSettingsEditorInput(configurationTarget, resource); options = { ...options, pinned: true }; - return await this.editorService.openEditor(editableSettingsEditorInput, validateSettingsEditorOptions(options), group); + return await group.openEditor(editableSettingsEditorInput, { ...validateSettingsEditorOptions(options) }); } - private async doOpenSplitJSON(resource: URI, options: ISettingsEditorOptions = {}, group?: SIDE_GROUP_TYPE): Promise { + private async doOpenSplitJSON(resource: URI, options: ISettingsEditorOptions = {}, group: IEditorGroup,): Promise { const configurationTarget = options.target ?? ConfigurationTarget.USER; await this.createSettingsIfNotExists(configurationTarget, resource); const preferencesEditorInput = this.createSplitJsonEditorInput(configurationTarget, resource); options = { ...options, pinned: true }; - return this.editorService.openEditor(preferencesEditorInput, validateSettingsEditorOptions(options), group); + return group.openEditor(preferencesEditorInput, validateSettingsEditorOptions(options)); } public createSplitJsonEditorInput(configurationTarget: ConfigurationTarget, resource: URI): EditorInput { diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index e4ae6416bbbb6..ea38554325257 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -211,6 +211,7 @@ export interface ISettingsEditorOptions extends IEditorOptions { export interface IOpenSettingsOptions extends ISettingsEditorOptions { jsonEditor?: boolean; openToSide?: boolean; + groupId?: number; } export function validateSettingsEditorOptions(options: ISettingsEditorOptions): ISettingsEditorOptions { @@ -231,6 +232,10 @@ export interface IKeybindingsEditorOptions extends IEditorOptions { query?: string; } +export interface IOpenKeybindingsEditorOptions extends IKeybindingsEditorOptions { + groupId?: number; +} + export const IPreferencesService = createDecorator('preferencesService'); export interface IPreferencesService { @@ -254,7 +259,7 @@ export interface IPreferencesService { openRemoteSettings(options?: IOpenSettingsOptions): Promise; openWorkspaceSettings(options?: IOpenSettingsOptions): Promise; openFolderSettings(options: IOpenSettingsOptions & { folderUri: IOpenSettingsOptions['folderUri'] }): Promise; - openGlobalKeybindingSettings(textual: boolean, options?: IKeybindingsEditorOptions): Promise; + openGlobalKeybindingSettings(textual: boolean, options?: IOpenKeybindingsEditorOptions): Promise; openDefaultKeybindingsFile(): Promise; openLanguageSpecificSettings(languageId: string, options?: IOpenSettingsOptions): Promise; getEditableSettingsURI(configurationTarget: ConfigurationTarget, resource?: URI): Promise; diff --git a/src/vs/workbench/services/preferences/test/browser/preferencesService.test.ts b/src/vs/workbench/services/preferences/test/browser/preferencesService.test.ts index 4f1f2493cb4f7..77c36590b8a36 100644 --- a/src/vs/workbench/services/preferences/test/browser/preferencesService.test.ts +++ b/src/vs/workbench/services/preferences/test/browser/preferencesService.test.ts @@ -10,26 +10,37 @@ import { ICommandService } from '../../../../../platform/commands/common/command import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js'; import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js'; import { IURLService } from '../../../../../platform/url/common/url.js'; -import { DEFAULT_EDITOR_ASSOCIATION } from '../../../../common/editor.js'; +import { DEFAULT_EDITOR_ASSOCIATION, IEditorPane } from '../../../../common/editor.js'; import { IJSONEditingService } from '../../../configuration/common/jsonEditing.js'; import { TestJSONEditingService } from '../../../configuration/test/common/testServices.js'; import { PreferencesService } from '../../browser/preferencesService.js'; import { IPreferencesService, ISettingsEditorOptions } from '../../common/preferences.js'; import { IRemoteAgentService } from '../../../remote/common/remoteAgentService.js'; -import { TestRemoteAgentService, ITestInstantiationService, TestEditorService, workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; +import { TestRemoteAgentService, ITestInstantiationService, workbenchInstantiationService, TestEditorGroupView, TestEditorGroupsService } from '../../../../test/browser/workbenchTestServices.js'; +import { IEditorGroupsService } from '../../../editor/common/editorGroupsService.js'; +import { IEditorOptions } from '../../../../../platform/editor/common/editor.js'; +import { SettingsEditor2Input } from '../../common/preferencesEditorInput.js'; suite('PreferencesService', () => { let testInstantiationService: ITestInstantiationService; let testObject: PreferencesService; - let editorService: TestEditorService2; + let lastOpenEditorOptions: IEditorOptions | undefined; const disposables = ensureNoDisposablesAreLeakedInTestSuite(); setup(() => { - editorService = disposables.add(new TestEditorService2()); - testInstantiationService = workbenchInstantiationService({ - editorService: () => editorService - }, disposables); + testInstantiationService = workbenchInstantiationService({}, disposables); + class TestOpenEditorGroupView extends TestEditorGroupView { + lastOpenEditorOptions: any; + override openEditor(_editor: SettingsEditor2Input, options?: IEditorOptions): Promise { + lastOpenEditorOptions = options; + _editor.dispose(); + return Promise.resolve(undefined!); + } + } + + const testEditorGroupService = new TestEditorGroupsService([new TestOpenEditorGroupView(0)]); + testInstantiationService.stub(IEditorGroupsService, testEditorGroupService); testInstantiationService.stub(IJSONEditingService, TestJSONEditingService); testInstantiationService.stub(IRemoteAgentService, TestRemoteAgentService); testInstantiationService.stub(ICommandService, TestCommandService); @@ -42,19 +53,10 @@ suite('PreferencesService', () => { testObject = disposables.add(instantiationService.createInstance(PreferencesService)); }); test('options are preserved when calling openEditor', async () => { - testObject.openSettings({ jsonEditor: false, query: 'test query' }); - const options = editorService.lastOpenEditorOptions as ISettingsEditorOptions; + await testObject.openSettings({ jsonEditor: false, query: 'test query' }); + const options = lastOpenEditorOptions as ISettingsEditorOptions; assert.strictEqual(options.focusSearch, true); assert.strictEqual(options.override, DEFAULT_EDITOR_ASSOCIATION.id); assert.strictEqual(options.query, 'test query'); }); }); - -class TestEditorService2 extends TestEditorService { - lastOpenEditorOptions: any; - - override async openEditor(editorInput: any, options?: any): Promise { - this.lastOpenEditorOptions = options; - return super.openEditor(editorInput, options); - } -} From 3e86f1e6cd9bf88b1ce23192fd0b68380bfc69be Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 17 Dec 2024 16:38:47 -0800 Subject: [PATCH 067/200] cli: fix serve-web needs to wait a certain amount of time after machine startup (#236427) Fixes #233155 --- cli/src/commands/serve_web.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cli/src/commands/serve_web.rs b/cli/src/commands/serve_web.rs index 3dedf4b3f059d..ddef3eef3421f 100644 --- a/cli/src/commands/serve_web.rs +++ b/cli/src/commands/serve_web.rs @@ -548,9 +548,11 @@ impl ConnectionManager { Err(_) => Quality::Stable, }); + let now = Instant::now(); let latest_version = tokio::sync::Mutex::new(cache.get().first().map(|latest_commit| { ( - Instant::now() - Duration::from_secs(RELEASE_CHECK_INTERVAL), + now.checked_sub(Duration::from_secs(RELEASE_CHECK_INTERVAL)) + .unwrap_or(now), // handle 0-ish instants, #233155 Release { name: String::from("0.0.0"), // Version information not stored on cache commit: latest_commit.clone(), From 20899216df0283a9e29f1e45badeb3a8714e6b99 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2024 07:26:26 +0100 Subject: [PATCH 068/200] dialogs - add `Cmd+D` handling (fix #71430) (#236434) --- src/vs/base/browser/ui/dialog/dialog.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 5fcebbd34c6c5..2ed8d24fadf73 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -275,6 +275,22 @@ export class Dialog extends Disposable { return; // leave default handling } + // Cmd+D (trigger the "no"/"do not save"-button) (macOS only) + if (isMacintosh && evt.equals(KeyMod.CtrlCmd | KeyCode.KeyD)) { + EventHelper.stop(e); + + const noButton = buttonMap.find(button => button.index === 1 && button.index !== this.options.cancelId); + if (noButton) { + resolve({ + button: noButton.index, + checkboxChecked: this.checkbox ? this.checkbox.checked : undefined, + values: this.inputs.length > 0 ? this.inputs.map(input => input.value) : undefined + }); + } + + return; // leave default handling + } + if (evt.equals(KeyCode.Space)) { return; // leave default handling } From 95d0e288d11737685fc91eb083bdc2abb8cd6d78 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2024 07:43:11 +0100 Subject: [PATCH 069/200] fuzzy - add test coverage for path matching (#236435) --- src/vs/base/test/common/fuzzyScorer.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/vs/base/test/common/fuzzyScorer.test.ts b/src/vs/base/test/common/fuzzyScorer.test.ts index b180df4422bf3..dc30534270fce 100644 --- a/src/vs/base/test/common/fuzzyScorer.test.ts +++ b/src/vs/base/test/common/fuzzyScorer.test.ts @@ -1081,6 +1081,21 @@ suite('Fuzzy Scorer', () => { } }); + test('compareFilesByScore - skip preference on label match when using path sep', function () { + const resourceA = URI.file('djangosite/ufrela/def.py'); + const resourceB = URI.file('djangosite/urls/default.py'); + + for (const query of ['url/def']) { + let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); + + res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); + assert.strictEqual(res[0], resourceB); + assert.strictEqual(res[1], resourceA); + } + }); + test('compareFilesByScore - boost shorter prefix match if multiple queries are used (#99171)', function () { const resourceA = URI.file('mesh_editor_lifetime_job.h'); const resourceB = URI.file('lifetime_job.h'); From cf2ebd91b8e42e3bc5ab0e85e3323c886a977ffe Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2024 07:45:55 +0100 Subject: [PATCH 070/200] Revert "debt: clean up obsolete file usage" (#236433) Revert "debt: clean up obsolete file usage (#236379)" This reverts commit 625bae23758002b62954fd10b53d25409421d718. --- .../common/extensionManagement.ts | 4 +- .../common/extensionsScannerService.ts | 51 ++++-- .../node/extensionManagementService.ts | 163 +++++++++--------- .../node/extensionsWatcher.ts | 20 +-- .../node/extensionsScannerService.test.ts | 33 +++- 5 files changed, 156 insertions(+), 115 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index f08b46ae65b30..155079831fcc7 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -456,8 +456,8 @@ export const enum ExtensionManagementErrorCode { Extract = 'Extract', Scanning = 'Scanning', ScanningExtension = 'ScanningExtension', - ReadRemoved = 'ReadRemoved', - UnsetRemoved = 'UnsetRemoved', + ReadUninstalled = 'ReadUninstalled', + UnsetUninstalled = 'UnsetUninstalled', Delete = 'Delete', Rename = 'Rename', IntializeDefaultProfile = 'IntializeDefaultProfile', diff --git a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts index a186b6fa04567..99868cddb5d63 100644 --- a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts @@ -7,6 +7,7 @@ import { coalesce } from '../../../base/common/arrays.js'; import { ThrottledDelayer } from '../../../base/common/async.js'; import * as objects from '../../../base/common/objects.js'; import { VSBuffer } from '../../../base/common/buffer.js'; +import { IStringDictionary } from '../../../base/common/collections.js'; import { getErrorMessage } from '../../../base/common/errors.js'; import { getNodeType, parse, ParseError } from '../../../base/common/json.js'; import { getParseErrorMessage } from '../../../base/common/jsonErrorMessages.js'; @@ -17,11 +18,12 @@ import * as platform from '../../../base/common/platform.js'; import { basename, isEqual, joinPath } from '../../../base/common/resources.js'; import * as semver from '../../../base/common/semver/semver.js'; import Severity from '../../../base/common/severity.js'; +import { isEmptyObject } from '../../../base/common/types.js'; import { URI } from '../../../base/common/uri.js'; import { localize } from '../../../nls.js'; import { IEnvironmentService } from '../../environment/common/environment.js'; import { IProductVersion, Metadata } from './extensionManagement.js'; -import { areSameExtensions, computeTargetPlatform, getExtensionId, getGalleryExtensionId } from './extensionManagementUtil.js'; +import { areSameExtensions, computeTargetPlatform, ExtensionKey, getExtensionId, getGalleryExtensionId } from './extensionManagementUtil.js'; import { ExtensionType, ExtensionIdentifier, IExtensionManifest, TargetPlatform, IExtensionIdentifier, IRelaxedExtensionManifest, UNDEFINED_PUBLISHER, IExtensionDescription, BUILTIN_MANIFEST_CACHE_FILE, USER_MANIFEST_CACHE_FILE, ExtensionIdentifierMap, parseEnabledApiProposalNames } from '../../extensions/common/extensions.js'; import { validateExtensionManifest } from '../../extensions/common/extensionValidator.js'; import { FileOperationResult, IFileService, toFileOperationResult } from '../../files/common/files.js'; @@ -104,6 +106,7 @@ export type ScanOptions = { readonly profileLocation?: URI; readonly includeInvalid?: boolean; readonly includeAllVersions?: boolean; + readonly includeUninstalled?: boolean; readonly checkControlFile?: boolean; readonly language?: string; readonly useCache?: boolean; @@ -142,9 +145,10 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem private readonly _onDidChangeCache = this._register(new Emitter()); readonly onDidChangeCache = this._onDidChangeCache.event; - private readonly systemExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, this.currentProfile)); - private readonly userExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, this.currentProfile)); - private readonly extensionsScanner = this._register(this.instantiationService.createInstance(ExtensionsScanner)); + private readonly obsoleteFile = joinPath(this.userExtensionsLocation, '.obsolete'); + private readonly systemExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, this.currentProfile, this.obsoleteFile)); + private readonly userExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, this.currentProfile, this.obsoleteFile)); + private readonly extensionsScanner = this._register(this.instantiationService.createInstance(ExtensionsScanner, this.obsoleteFile)); constructor( readonly systemExtensionsLocation: URI, @@ -195,8 +199,8 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem const location = scanOptions.profileLocation ?? this.userExtensionsLocation; this.logService.trace('Started scanning user extensions', location); const profileScanOptions: IProfileExtensionsScanOptions | undefined = this.uriIdentityService.extUri.isEqual(scanOptions.profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource) ? { bailOutWhenFileNotFound: true } : undefined; - const extensionsScannerInput = await this.createExtensionScannerInput(location, !!scanOptions.profileLocation, ExtensionType.User, scanOptions.language, true, profileScanOptions, scanOptions.productVersion ?? this.getProductVersion()); - const extensionsScanner = scanOptions.useCache && !extensionsScannerInput.devMode ? this.userExtensionsCachedScanner : this.extensionsScanner; + const extensionsScannerInput = await this.createExtensionScannerInput(location, !!scanOptions.profileLocation, ExtensionType.User, !scanOptions.includeUninstalled, scanOptions.language, true, profileScanOptions, scanOptions.productVersion ?? this.getProductVersion()); + const extensionsScanner = scanOptions.useCache && !extensionsScannerInput.devMode && extensionsScannerInput.excludeObsolete ? this.userExtensionsCachedScanner : this.extensionsScanner; let extensions: IRelaxedScannedExtension[]; try { extensions = await extensionsScanner.scanExtensions(extensionsScannerInput); @@ -217,7 +221,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem if (this.environmentService.isExtensionDevelopment && this.environmentService.extensionDevelopmentLocationURI) { const extensions = (await Promise.all(this.environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file) .map(async extensionDevelopmentLocationURI => { - const input = await this.createExtensionScannerInput(extensionDevelopmentLocationURI, false, ExtensionType.User, scanOptions.language, false /* do not validate */, undefined, scanOptions.productVersion ?? this.getProductVersion()); + const input = await this.createExtensionScannerInput(extensionDevelopmentLocationURI, false, ExtensionType.User, true, scanOptions.language, false /* do not validate */, undefined, scanOptions.productVersion ?? this.getProductVersion()); const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(input); return extensions.map(extension => { // Override the extension type from the existing extensions @@ -233,7 +237,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } async scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise { - const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion()); + const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, true, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion()); const extension = await this.extensionsScanner.scanExtension(extensionsScannerInput); if (!extension) { return null; @@ -245,7 +249,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } async scanOneOrMultipleExtensions(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise { - const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion()); + const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, true, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion()); const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(extensionsScannerInput); return this.applyScanOptions(extensions, extensionType, scanOptions, true); } @@ -401,7 +405,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem private async scanDefaultSystemExtensions(useCache: boolean, language: string | undefined): Promise { this.logService.trace('Started scanning system extensions'); - const extensionsScannerInput = await this.createExtensionScannerInput(this.systemExtensionsLocation, false, ExtensionType.System, language, true, undefined, this.getProductVersion()); + const extensionsScannerInput = await this.createExtensionScannerInput(this.systemExtensionsLocation, false, ExtensionType.System, true, language, true, undefined, this.getProductVersion()); const extensionsScanner = useCache && !extensionsScannerInput.devMode ? this.systemExtensionsCachedScanner : this.extensionsScanner; const result = await extensionsScanner.scanExtensions(extensionsScannerInput); this.logService.trace('Scanned system extensions:', result.length); @@ -431,7 +435,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem break; } } - const result = await Promise.all(devSystemExtensionsLocations.map(async location => this.extensionsScanner.scanExtension((await this.createExtensionScannerInput(location, false, ExtensionType.System, language, true, undefined, this.getProductVersion()))))); + const result = await Promise.all(devSystemExtensionsLocations.map(async location => this.extensionsScanner.scanExtension((await this.createExtensionScannerInput(location, false, ExtensionType.System, true, language, true, undefined, this.getProductVersion()))))); this.logService.trace('Scanned dev system extensions:', result.length); return coalesce(result); } @@ -445,7 +449,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } } - private async createExtensionScannerInput(location: URI, profile: boolean, type: ExtensionType, language: string | undefined, validate: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined, productVersion: IProductVersion): Promise { + private async createExtensionScannerInput(location: URI, profile: boolean, type: ExtensionType, excludeObsolete: boolean, language: string | undefined, validate: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined, productVersion: IProductVersion): Promise { const translations = await this.getTranslations(language ?? platform.language); const mtime = await this.getMtime(location); const applicationExtensionsLocation = profile && !this.uriIdentityService.extUri.isEqual(location, this.userDataProfilesService.defaultProfile.extensionsResource) ? this.userDataProfilesService.defaultProfile.extensionsResource : undefined; @@ -458,6 +462,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem profile, profileScanOptions, type, + excludeObsolete, validate, productVersion.version, productVersion.date, @@ -499,6 +504,7 @@ export class ExtensionScannerInput { public readonly profile: boolean, public readonly profileScanOptions: IProfileExtensionsScanOptions | undefined, public readonly type: ExtensionType, + public readonly excludeObsolete: boolean, public readonly validate: boolean, public readonly productVersion: string, public readonly productDate: string | undefined, @@ -528,6 +534,7 @@ export class ExtensionScannerInput { && a.profile === b.profile && objects.equals(a.profileScanOptions, b.profileScanOptions) && a.type === b.type + && a.excludeObsolete === b.excludeObsolete && a.validate === b.validate && a.productVersion === b.productVersion && a.productDate === b.productDate @@ -551,6 +558,7 @@ class ExtensionsScanner extends Disposable { private readonly extensionsEnabledWithApiProposalVersion: string[]; constructor( + private readonly obsoleteFile: URI, @IExtensionsProfileScannerService protected readonly extensionsProfileScannerService: IExtensionsProfileScannerService, @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService, @IFileService protected readonly fileService: IFileService, @@ -563,9 +571,15 @@ class ExtensionsScanner extends Disposable { } async scanExtensions(input: ExtensionScannerInput): Promise { - return input.profile - ? this.scanExtensionsFromProfile(input) - : this.scanExtensionsFromLocation(input); + const extensions = input.profile ? await this.scanExtensionsFromProfile(input) : await this.scanExtensionsFromLocation(input); + let obsolete: IStringDictionary = {}; + if (input.excludeObsolete && input.type === ExtensionType.User) { + try { + const raw = (await this.fileService.readFile(this.obsoleteFile)).value.toString(); + obsolete = JSON.parse(raw); + } catch (error) { /* ignore */ } + } + return isEmptyObject(obsolete) ? extensions : extensions.filter(e => !obsolete[ExtensionKey.create(e).toString()]); } private async scanExtensionsFromLocation(input: ExtensionScannerInput): Promise { @@ -582,7 +596,7 @@ class ExtensionsScanner extends Disposable { if (input.type === ExtensionType.User && basename(c.resource).indexOf('.') === 0) { return null; } - const extensionScannerInput = new ExtensionScannerInput(c.resource, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations); + const extensionScannerInput = new ExtensionScannerInput(c.resource, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.excludeObsolete, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations); return this.scanExtension(extensionScannerInput); })); return coalesce(extensions) @@ -608,7 +622,7 @@ class ExtensionsScanner extends Disposable { const extensions = await Promise.all( scannedProfileExtensions.map(async extensionInfo => { if (filter(extensionInfo)) { - const extensionScannerInput = new ExtensionScannerInput(extensionInfo.location, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations); + const extensionScannerInput = new ExtensionScannerInput(extensionInfo.location, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.excludeObsolete, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations); return this.scanExtension(extensionScannerInput, extensionInfo.metadata); } return null; @@ -877,6 +891,7 @@ class CachedExtensionsScanner extends ExtensionsScanner { constructor( private readonly currentProfile: IUserDataProfile, + obsoleteFile: URI, @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @IExtensionsProfileScannerService extensionsProfileScannerService: IExtensionsProfileScannerService, @IUriIdentityService uriIdentityService: IUriIdentityService, @@ -885,7 +900,7 @@ class CachedExtensionsScanner extends ExtensionsScanner { @IEnvironmentService environmentService: IEnvironmentService, @ILogService logService: ILogService ) { - super(extensionsProfileScannerService, uriIdentityService, fileService, productService, environmentService, logService); + super(obsoleteFile, extensionsProfileScannerService, uriIdentityService, fileService, productService, environmentService, logService); } override async scanExtensions(input: ExtensionScannerInput): Promise { diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 015c3dff393a0..92405eefb753c 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -60,7 +60,7 @@ export interface INativeServerExtensionManagementService extends IExtensionManag readonly _serviceBrand: undefined; scanAllUserInstalledExtensions(): Promise; scanInstalledExtensionAtLocation(location: URI): Promise; - deleteExtensions(...extensions: IExtension[]): Promise; + markAsUninstalled(...extensions: IExtension[]): Promise; } type ExtractExtensionResult = { readonly local: ILocalExtension; readonly verificationStatus?: ExtensionSignatureVerificationCode }; @@ -222,8 +222,8 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return this.extensionsScanner.copyExtensions(fromProfileLocation, toProfileLocation, { version: this.productService.version, date: this.productService.date }); } - deleteExtensions(...extensions: IExtension[]): Promise { - return this.extensionsScanner.setExtensionsForRemoval(...extensions); + markAsUninstalled(...extensions: IExtension[]): Promise { + return this.extensionsScanner.setUninstalled(...extensions); } async cleanUp(): Promise { @@ -480,20 +480,8 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi continue; } - // Ignore changes to the deleted folder - if (this.uriIdentityService.extUri.basename(resource).endsWith(DELETED_FOLDER_POSTFIX)) { - continue; - } - - try { - // Check if this is a directory - if (!(await this.fileService.stat(resource)).isDirectory) { - continue; - } - } catch (error) { - if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { - this.logService.error(error); - } + // Check if this is a directory + if (!(await this.fileService.stat(resource)).isDirectory) { continue; } @@ -514,10 +502,23 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi private async addExtensionsToProfile(extensions: [ILocalExtension, Metadata | undefined][], profileLocation: URI): Promise { const localExtensions = extensions.map(e => e[0]); - await this.extensionsScanner.unsetExtensionsForRemoval(...localExtensions.map(extension => ExtensionKey.create(extension))); + await this.setInstalled(localExtensions); 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(); + 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); + await this.extensionsScanner.setInstalled(extensionKey); + this.logService.info('Removed the extension from uninstalled list:', extensionKey.id); + } + } } type UpdateMetadataErrorClassification = { @@ -535,8 +536,8 @@ type UpdateMetadataErrorEvent = { export class ExtensionsScanner extends Disposable { - private readonly obsoletedResource: URI; - private readonly obsoleteFileLimiter: Queue; + private readonly uninstalledResource: URI; + private readonly uninstalledFileLimiter: Queue; private readonly _onExtract = this._register(new Emitter()); readonly onExtract = this._onExtract.event; @@ -554,13 +555,13 @@ export class ExtensionsScanner extends Disposable { @ILogService private readonly logService: ILogService, ) { super(); - this.obsoletedResource = joinPath(this.extensionsScannerService.userExtensionsLocation, '.obsolete'); - this.obsoleteFileLimiter = new Queue(); + this.uninstalledResource = joinPath(this.extensionsScannerService.userExtensionsLocation, '.obsolete'); + this.uninstalledFileLimiter = new Queue(); } async cleanUp(): Promise { await this.removeTemporarilyDeletedFolders(); - await this.deleteExtensionsMarkedForRemoval(); + await this.removeUninstalledExtensions(); await this.initializeMetadata(); } @@ -719,38 +720,40 @@ export class ExtensionsScanner extends Disposable { return this.scanLocalExtension(local.location, local.type, profileLocation); } - async setExtensionsForRemoval(...extensions: IExtension[]): Promise { + async getUninstalledExtensions(): Promise> { + try { + return await this.withUninstalledExtensions(); + } catch (error) { + throw toExtensionManagementError(error, ExtensionManagementErrorCode.ReadUninstalled); + } + } + + async setUninstalled(...extensions: IExtension[]): Promise { const extensionKeys: ExtensionKey[] = extensions.map(e => ExtensionKey.create(e)); - await this.withRemovedExtensions(removedExtensions => + await this.withUninstalledExtensions(uninstalled => extensionKeys.forEach(extensionKey => { - removedExtensions[extensionKey.toString()] = true; - this.logService.info('Marked extension as removed', extensionKey.toString()); + uninstalled[extensionKey.toString()] = true; + this.logService.info('Marked extension as uninstalled', extensionKey.toString()); })); } - async unsetExtensionsForRemoval(...extensionKeys: ExtensionKey[]): Promise { + async setInstalled(extensionKey: ExtensionKey): Promise { try { - const results: boolean[] = []; - await this.withRemovedExtensions(removedExtensions => - extensionKeys.forEach(extensionKey => { - if (removedExtensions[extensionKey.toString()]) { - results.push(true); - delete removedExtensions[extensionKey.toString()]; - } else { - results.push(false); - } - })); - return results; + await this.withUninstalledExtensions(uninstalled => delete uninstalled[extensionKey.toString()]); } catch (error) { - throw toExtensionManagementError(error, ExtensionManagementErrorCode.UnsetRemoved); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.UnsetUninstalled); } } - async deleteExtension(extension: ILocalExtension | IScannedExtension, type: string): Promise { + 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); } - await this.unsetExtensionsForRemoval(ExtensionKey.create(extension)); + } + + 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 { @@ -789,11 +792,11 @@ export class ExtensionsScanner extends Disposable { this.logService.info(`Deleted ${type} extension from disk`, id, location.fsPath); } - private withRemovedExtensions(updateFn?: (removed: IStringDictionary) => void): Promise> { - return this.obsoleteFileLimiter.queue(async () => { + private withUninstalledExtensions(updateFn?: (uninstalled: IStringDictionary) => void): Promise> { + return this.uninstalledFileLimiter.queue(async () => { let raw: string | undefined; try { - const content = await this.fileService.readFile(this.obsoletedResource, 'utf8'); + const content = await this.fileService.readFile(this.uninstalledResource, 'utf8'); raw = content.value.toString(); } catch (error) { if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { @@ -801,23 +804,23 @@ export class ExtensionsScanner extends Disposable { } } - let removed = {}; + let uninstalled = {}; if (raw) { try { - removed = JSON.parse(raw); + uninstalled = JSON.parse(raw); } catch (e) { /* ignore */ } } if (updateFn) { - updateFn(removed); - if (Object.keys(removed).length) { - await this.fileService.writeFile(this.obsoletedResource, VSBuffer.fromString(JSON.stringify(removed))); + updateFn(uninstalled); + if (Object.keys(uninstalled).length) { + await this.fileService.writeFile(this.uninstalledResource, VSBuffer.fromString(JSON.stringify(uninstalled))); } else { - await this.fileService.del(this.obsoletedResource); + await this.fileService.del(this.uninstalledResource); } } - return removed; + return uninstalled; }); } @@ -895,25 +898,19 @@ export class ExtensionsScanner extends Disposable { })); } - private async deleteExtensionsMarkedForRemoval(): Promise { - let removed: IStringDictionary; - try { - removed = await this.withRemovedExtensions(); - } catch (error) { - throw toExtensionManagementError(error, ExtensionManagementErrorCode.ReadRemoved); - } - - if (Object.keys(removed).length === 0) { - this.logService.debug(`No extensions are marked as removed.`); + private async removeUninstalledExtensions(): Promise { + const uninstalled = await this.getUninstalledExtensions(); + if (Object.keys(uninstalled).length === 0) { + this.logService.debug(`No uninstalled extensions found.`); return; } - this.logService.debug(`Deleting extensions marked as removed:`, Object.keys(removed)); + this.logService.debug(`Removing uninstalled extensions:`, Object.keys(uninstalled)); - const extensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: true, includeInvalid: true }); // All user extensions + 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 (!removed[ExtensionKey.create(e).toString()]) { + if (!uninstalled[ExtensionKey.create(e).toString()]) { installed.add(e.identifier.id.toLowerCase()); } } @@ -931,8 +928,8 @@ export class ExtensionsScanner extends Disposable { this.logService.error(error); } - const toRemove = extensions.filter(e => e.metadata /* Installed by System */ && removed[ExtensionKey.create(e).toString()]); - await Promise.allSettled(toRemove.map(e => this.deleteExtension(e, 'marked for removal'))); + 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 { @@ -1024,7 +1021,7 @@ class InstallExtensionInProfileTask extends AbstractExtensionTask { - // If the same version of extension is marked as removed, remove it from there and return the local. - const [removed] = await this.extensionsScanner.unsetExtensionsForRemoval(extensionKey); - if (removed) { - this.logService.info('Removed the extension from removed list:', extensionKey.id); - const userExtensions = await this.extensionsScanner.scanAllUserExtensions(true); - return userExtensions.find(i => ExtensionKey.create(i).equals(extensionKey)); + private async unsetIfUninstalled(extensionKey: ExtensionKey): Promise { + const uninstalled = await this.extensionsScanner.getUninstalledExtensions(); + if (!uninstalled[extensionKey.toString()]) { + return undefined; } - return undefined; + + 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)); } private async updateMetadata(extension: ILocalExtension, token: CancellationToken): Promise { @@ -1148,8 +1149,8 @@ class UninstallExtensionInProfileTask extends AbstractExtensionTask implem super(); } - protected doRun(token: CancellationToken): Promise { - return this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension, this.options.profileLocation); + protected async doRun(token: CancellationToken): Promise { + await this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension, this.options.profileLocation); } } diff --git a/src/vs/platform/extensionManagement/node/extensionsWatcher.ts b/src/vs/platform/extensionManagement/node/extensionsWatcher.ts index 2c4e976a5a648..d2b65eaea553c 100644 --- a/src/vs/platform/extensionManagement/node/extensionsWatcher.ts +++ b/src/vs/platform/extensionManagement/node/extensionsWatcher.ts @@ -48,7 +48,7 @@ export class ExtensionsWatcher extends Disposable { await this.extensionsScannerService.initializeDefaultProfileExtensions(); await this.onDidChangeProfiles(this.userDataProfilesService.profiles); this.registerListeners(); - await this.deleteExtensionsNotInProfiles(); + await this.uninstallExtensionsNotInProfiles(); } private registerListeners(): void { @@ -102,7 +102,7 @@ export class ExtensionsWatcher extends Disposable { } private async onDidRemoveExtensions(e: DidRemoveProfileExtensionsEvent): Promise { - const extensionsToDelete: IExtension[] = []; + const extensionsToUninstall: IExtension[] = []; const promises: Promise[] = []; for (const extension of e.extensions) { const key = this.getKey(extension.identifier, extension.version); @@ -115,7 +115,7 @@ export class ExtensionsWatcher extends Disposable { promises.push(this.extensionManagementService.scanInstalledExtensionAtLocation(extension.location) .then(result => { if (result) { - extensionsToDelete.push(result); + extensionsToUninstall.push(result); } else { this.logService.info('Extension not found at the location', extension.location.toString()); } @@ -125,8 +125,8 @@ export class ExtensionsWatcher extends Disposable { } try { await Promise.all(promises); - if (extensionsToDelete.length) { - await this.deleteExtensionsNotInProfiles(extensionsToDelete); + if (extensionsToUninstall.length) { + await this.uninstallExtensionsNotInProfiles(extensionsToUninstall); } } catch (error) { this.logService.error(error); @@ -180,13 +180,13 @@ export class ExtensionsWatcher extends Disposable { } } - private async deleteExtensionsNotInProfiles(toDelete?: IExtension[]): Promise { - if (!toDelete) { + private async uninstallExtensionsNotInProfiles(toUninstall?: IExtension[]): Promise { + if (!toUninstall) { const installed = await this.extensionManagementService.scanAllUserInstalledExtensions(); - toDelete = installed.filter(installedExtension => !this.allExtensions.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version))); + toUninstall = installed.filter(installedExtension => !this.allExtensions.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version))); } - if (toDelete.length) { - await this.extensionManagementService.deleteExtensions(...toDelete); + if (toUninstall.length) { + await this.extensionManagementService.markAsUninstalled(...toUninstall); } } diff --git a/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts index 551ba576d4459..74d3ffcd738f8 100644 --- a/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts +++ b/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts @@ -224,6 +224,31 @@ suite('NativeExtensionsScanerService Test', () => { assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' }); }); + test('scan exclude uninstalled extensions', async () => { + await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' })); + await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub' })); + await instantiationService.get(IFileService).writeFile(joinPath(URI.file(instantiationService.get(INativeEnvironmentService).extensionsPath), '.obsolete'), VSBuffer.fromString(JSON.stringify({ 'pub.name2-1.0.0': true }))); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); + + const actual = await testObject.scanUserExtensions({}); + + assert.deepStrictEqual(actual.length, 1); + assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' }); + }); + + test('scan include uninstalled extensions', async () => { + await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' })); + await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub' })); + await instantiationService.get(IFileService).writeFile(joinPath(URI.file(instantiationService.get(INativeEnvironmentService).extensionsPath), '.obsolete'), VSBuffer.fromString(JSON.stringify({ 'pub.name2-1.0.0': true }))); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); + + const actual = await testObject.scanUserExtensions({ includeUninstalled: true }); + + assert.deepStrictEqual(actual.length, 2); + assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' }); + assert.deepStrictEqual(actual[1].identifier, { id: 'pub.name2' }); + }); + test('scan include invalid extensions', async () => { await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' })); await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub', engines: { vscode: '^1.67.0' } })); @@ -326,7 +351,7 @@ suite('ExtensionScannerInput', () => { ensureNoDisposablesAreLeakedInTestSuite(); test('compare inputs - location', () => { - const anInput = (location: URI, mtime: number | undefined) => new ExtensionScannerInput(location, mtime, undefined, undefined, false, undefined, ExtensionType.User, true, '1.1.1', undefined, undefined, true, undefined, {}); + const anInput = (location: URI, mtime: number | undefined) => new ExtensionScannerInput(location, mtime, undefined, undefined, false, undefined, ExtensionType.User, true, true, '1.1.1', undefined, undefined, true, undefined, {}); assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, undefined), anInput(ROOT, undefined)), true); assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, 100), anInput(ROOT, 100)), true); @@ -336,7 +361,7 @@ suite('ExtensionScannerInput', () => { }); test('compare inputs - application location', () => { - const anInput = (location: URI, mtime: number | undefined) => new ExtensionScannerInput(ROOT, undefined, location, mtime, false, undefined, ExtensionType.User, true, '1.1.1', undefined, undefined, true, undefined, {}); + const anInput = (location: URI, mtime: number | undefined) => new ExtensionScannerInput(ROOT, undefined, location, mtime, false, undefined, ExtensionType.User, true, true, '1.1.1', undefined, undefined, true, undefined, {}); assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, undefined), anInput(ROOT, undefined)), true); assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, 100), anInput(ROOT, 100)), true); @@ -346,7 +371,7 @@ suite('ExtensionScannerInput', () => { }); test('compare inputs - profile', () => { - const anInput = (profile: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined) => new ExtensionScannerInput(ROOT, undefined, undefined, undefined, profile, profileScanOptions, ExtensionType.User, true, '1.1.1', undefined, undefined, true, undefined, {}); + const anInput = (profile: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined) => new ExtensionScannerInput(ROOT, undefined, undefined, undefined, profile, profileScanOptions, ExtensionType.User, true, true, '1.1.1', undefined, undefined, true, undefined, {}); assert.strictEqual(ExtensionScannerInput.equals(anInput(true, { bailOutWhenFileNotFound: true }), anInput(true, { bailOutWhenFileNotFound: true })), true); assert.strictEqual(ExtensionScannerInput.equals(anInput(false, { bailOutWhenFileNotFound: true }), anInput(false, { bailOutWhenFileNotFound: true })), true); @@ -359,7 +384,7 @@ suite('ExtensionScannerInput', () => { }); test('compare inputs - extension type', () => { - const anInput = (type: ExtensionType) => new ExtensionScannerInput(ROOT, undefined, undefined, undefined, false, undefined, type, true, '1.1.1', undefined, undefined, true, undefined, {}); + const anInput = (type: ExtensionType) => new ExtensionScannerInput(ROOT, undefined, undefined, undefined, false, undefined, type, true, true, '1.1.1', undefined, undefined, true, undefined, {}); assert.strictEqual(ExtensionScannerInput.equals(anInput(ExtensionType.System), anInput(ExtensionType.System)), true); assert.strictEqual(ExtensionScannerInput.equals(anInput(ExtensionType.User), anInput(ExtensionType.User)), true); From 83f695ad2ae4befe613dfd1bb1b1d8547d647d53 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 18 Dec 2024 09:29:54 +0100 Subject: [PATCH 071/200] Move selected editors instead of only activ ones (#236327) * move selected editors * :lipstick: * :lipstick: --- .../browser/parts/editor/editorActions.ts | 38 +++++++------- .../browser/parts/editor/editorCommands.ts | 52 ++++++++++++------- 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index be79b3f0af2f8..9d9766f4eb503 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -12,7 +12,7 @@ import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser import { GoFilter, IHistoryService } from '../../../services/history/common/history.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; -import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveCopyArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, COPY_ACTIVE_EDITOR_COMMAND_ID, SPLIT_EDITOR, TOGGLE_MAXIMIZE_EDITOR_GROUP, MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, MOVE_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID as NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID } from './editorCommands.js'; +import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, SelectedEditorsMoveCopyArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, COPY_ACTIVE_EDITOR_COMMAND_ID, SPLIT_EDITOR, TOGGLE_MAXIMIZE_EDITOR_GROUP, MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, MOVE_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID as NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID } from './editorCommands.js'; import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder, MergeGroupMode } from '../../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -1996,7 +1996,7 @@ export class MoveEditorLeftInGroupAction extends ExecuteCommandAction { }, f1: true, category: Categories.View - }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'left' } satisfies ActiveEditorMoveCopyArguments); + }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'left' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2015,7 +2015,7 @@ export class MoveEditorRightInGroupAction extends ExecuteCommandAction { }, f1: true, category: Categories.View - }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'right' } satisfies ActiveEditorMoveCopyArguments); + }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'right' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2034,7 +2034,7 @@ export class MoveEditorToPreviousGroupAction extends ExecuteCommandAction { }, f1: true, category: Categories.View, - }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'previous', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'previous', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2053,7 +2053,7 @@ export class MoveEditorToNextGroupAction extends ExecuteCommandAction { } }, category: Categories.View - }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'next', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'next', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2065,7 +2065,7 @@ export class MoveEditorToAboveGroupAction extends ExecuteCommandAction { title: localize2('moveEditorToAboveGroup', 'Move Editor into Group Above'), f1: true, category: Categories.View - }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'up', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'up', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2077,7 +2077,7 @@ export class MoveEditorToBelowGroupAction extends ExecuteCommandAction { title: localize2('moveEditorToBelowGroup', 'Move Editor into Group Below'), f1: true, category: Categories.View - }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'down', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'down', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2089,7 +2089,7 @@ export class MoveEditorToLeftGroupAction extends ExecuteCommandAction { title: localize2('moveEditorToLeftGroup', 'Move Editor into Left Group'), f1: true, category: Categories.View - }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'left', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'left', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2101,7 +2101,7 @@ export class MoveEditorToRightGroupAction extends ExecuteCommandAction { title: localize2('moveEditorToRightGroup', 'Move Editor into Right Group'), f1: true, category: Categories.View - }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'right', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'right', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2120,7 +2120,7 @@ export class MoveEditorToFirstGroupAction extends ExecuteCommandAction { } }, category: Categories.View - }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'first', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'first', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2139,7 +2139,7 @@ export class MoveEditorToLastGroupAction extends ExecuteCommandAction { } }, category: Categories.View - }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'last', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, MOVE_ACTIVE_EDITOR_COMMAND_ID, { to: 'last', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2151,7 +2151,7 @@ export class SplitEditorToPreviousGroupAction extends ExecuteCommandAction { title: localize2('splitEditorToPreviousGroup', 'Split Editor into Previous Group'), f1: true, category: Categories.View - }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'previous', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'previous', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2163,7 +2163,7 @@ export class SplitEditorToNextGroupAction extends ExecuteCommandAction { title: localize2('splitEditorToNextGroup', 'Split Editor into Next Group'), f1: true, category: Categories.View - }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'next', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'next', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2175,7 +2175,7 @@ export class SplitEditorToAboveGroupAction extends ExecuteCommandAction { title: localize2('splitEditorToAboveGroup', 'Split Editor into Group Above'), f1: true, category: Categories.View - }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'up', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'up', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2187,7 +2187,7 @@ export class SplitEditorToBelowGroupAction extends ExecuteCommandAction { title: localize2('splitEditorToBelowGroup', 'Split Editor into Group Below'), f1: true, category: Categories.View - }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'down', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'down', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2202,7 +2202,7 @@ export class SplitEditorToLeftGroupAction extends ExecuteCommandAction { title: localize2('splitEditorToLeftGroup', "Split Editor into Left Group"), f1: true, category: Categories.View - }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'left', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'left', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2214,7 +2214,7 @@ export class SplitEditorToRightGroupAction extends ExecuteCommandAction { title: localize2('splitEditorToRightGroup', 'Split Editor into Right Group'), f1: true, category: Categories.View - }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'right', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'right', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2226,7 +2226,7 @@ export class SplitEditorToFirstGroupAction extends ExecuteCommandAction { title: localize2('splitEditorToFirstGroup', 'Split Editor into First Group'), f1: true, category: Categories.View - }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'first', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'first', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } @@ -2238,7 +2238,7 @@ export class SplitEditorToLastGroupAction extends ExecuteCommandAction { title: localize2('splitEditorToLastGroup', 'Split Editor into Last Group'), f1: true, category: Categories.View - }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'last', by: 'group' } satisfies ActiveEditorMoveCopyArguments); + }, COPY_ACTIVE_EDITOR_COMMAND_ID, { to: 'last', by: 'group' } satisfies SelectedEditorsMoveCopyArguments); } } diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 83f88bd7b749d..1bbe6258eb05e 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -28,7 +28,7 @@ import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from './editorQuickAc import { SideBySideEditor } from './sideBySideEditor.js'; import { TextDiffEditor } from './textDiffEditor.js'; import { ActiveEditorCanSplitInGroupContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupLockedContext, ActiveEditorStickyContext, MultipleEditorGroupsContext, SideBySideEditorActiveContext, TextCompareEditorActiveContext } from '../../../common/contextkeys.js'; -import { CloseDirection, EditorInputCapabilities, EditorsOrder, IResourceDiffEditorInput, IUntitledTextResourceEditorInput, IVisibleEditorPane, isEditorInputWithOptionsAndGroup } from '../../../common/editor.js'; +import { CloseDirection, EditorInputCapabilities, EditorsOrder, IResourceDiffEditorInput, IUntitledTextResourceEditorInput, isEditorInputWithOptionsAndGroup } from '../../../common/editor.js'; import { DiffEditorInput } from '../../../common/editor/diffEditorInput.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; import { SideBySideEditorInput } from '../../../common/editor/sideBySideEditorInput.js'; @@ -108,13 +108,13 @@ export const EDITOR_CORE_NAVIGATION_COMMANDS = [ TOGGLE_MAXIMIZE_EDITOR_GROUP ]; -export interface ActiveEditorMoveCopyArguments { +export interface SelectedEditorsMoveCopyArguments { to?: 'first' | 'last' | 'left' | 'right' | 'up' | 'down' | 'center' | 'position' | 'previous' | 'next'; by?: 'tab' | 'group'; value?: number; } -const isActiveEditorMoveCopyArg = function (arg: ActiveEditorMoveCopyArguments): boolean { +const isSelectedEditorsMoveCopyArg = function (arg: SelectedEditorsMoveCopyArguments): boolean { if (!isObject(arg)) { return false; } @@ -159,14 +159,14 @@ function registerActiveEditorMoveCopyCommand(): void { weight: KeybindingWeight.WorkbenchContrib, when: EditorContextKeys.editorTextFocus, primary: 0, - handler: (accessor, args) => moveCopyActiveEditor(true, args, accessor), + handler: (accessor, args) => moveCopySelectedEditors(true, args, accessor), metadata: { description: localize('editorCommand.activeEditorMove.description', "Move the active editor by tabs or groups"), args: [ { name: localize('editorCommand.activeEditorMove.arg.name', "Active editor move argument"), description: localize('editorCommand.activeEditorMove.arg.description', "Argument Properties:\n\t* 'to': String value providing where to move.\n\t* 'by': String value providing the unit for move (by tab or by group).\n\t* 'value': Number value providing how many positions or an absolute position to move."), - constraint: isActiveEditorMoveCopyArg, + constraint: isSelectedEditorsMoveCopyArg, schema: moveCopyJSONSchema } ] @@ -178,42 +178,55 @@ function registerActiveEditorMoveCopyCommand(): void { weight: KeybindingWeight.WorkbenchContrib, when: EditorContextKeys.editorTextFocus, primary: 0, - handler: (accessor, args) => moveCopyActiveEditor(false, args, accessor), + handler: (accessor, args) => moveCopySelectedEditors(false, args, accessor), metadata: { description: localize('editorCommand.activeEditorCopy.description', "Copy the active editor by groups"), args: [ { name: localize('editorCommand.activeEditorCopy.arg.name', "Active editor copy argument"), description: localize('editorCommand.activeEditorCopy.arg.description', "Argument Properties:\n\t* 'to': String value providing where to copy.\n\t* 'value': Number value providing how many positions or an absolute position to copy."), - constraint: isActiveEditorMoveCopyArg, + constraint: isSelectedEditorsMoveCopyArg, schema: moveCopyJSONSchema } ] } }); - function moveCopyActiveEditor(isMove: boolean, args: ActiveEditorMoveCopyArguments = Object.create(null), accessor: ServicesAccessor): void { + function moveCopySelectedEditors(isMove: boolean, args: SelectedEditorsMoveCopyArguments = Object.create(null), accessor: ServicesAccessor): void { args.to = args.to || 'right'; args.by = args.by || 'tab'; args.value = typeof args.value === 'number' ? args.value : 1; - const activeEditorPane = accessor.get(IEditorService).activeEditorPane; - if (activeEditorPane) { + const activeGroup = accessor.get(IEditorGroupsService).activeGroup; + const selectedEditors = activeGroup.selectedEditors; + if (selectedEditors.length > 0) { switch (args.by) { case 'tab': if (isMove) { - return moveActiveTab(args, activeEditorPane); + return moveTabs(args, activeGroup, selectedEditors); } break; case 'group': - return moveCopyActiveEditorToGroup(isMove, args, activeEditorPane, accessor); + return moveCopyActiveEditorToGroup(isMove, args, activeGroup, selectedEditors, accessor); } } } - function moveActiveTab(args: ActiveEditorMoveCopyArguments, control: IVisibleEditorPane): void { - const group = control.group; - let index = group.getIndexOfEditor(control.input); + function moveTabs(args: SelectedEditorsMoveCopyArguments, group: IEditorGroup, editors: EditorInput[]): void { + const to = args.to; + if (to === 'first' || to === 'right') { + editors = [...editors].reverse(); + } else if (to === 'position' && (args.value ?? 1) < group.getIndexOfEditor(editors[0])) { + editors = [...editors].reverse(); + } + + for (const editor of editors) { + moveTab(args, group, editor); + } + } + + function moveTab(args: SelectedEditorsMoveCopyArguments, group: IEditorGroup, editor: EditorInput): void { + let index = group.getIndexOfEditor(editor); switch (args.to) { case 'first': index = 0; @@ -236,14 +249,13 @@ function registerActiveEditorMoveCopyCommand(): void { } index = index < 0 ? 0 : index >= group.count ? group.count - 1 : index; - group.moveEditor(control.input, group, { index }); + group.moveEditor(editor, group, { index }); } - function moveCopyActiveEditorToGroup(isMove: boolean, args: ActiveEditorMoveCopyArguments, control: IVisibleEditorPane, accessor: ServicesAccessor): void { + function moveCopyActiveEditorToGroup(isMove: boolean, args: SelectedEditorsMoveCopyArguments, sourceGroup: IEditorGroup, editors: EditorInput[], accessor: ServicesAccessor): void { const editorGroupsService = accessor.get(IEditorGroupsService); const configurationService = accessor.get(IConfigurationService); - const sourceGroup = control.group; let targetGroup: IEditorGroup | undefined; switch (args.to) { @@ -296,9 +308,9 @@ function registerActiveEditorMoveCopyCommand(): void { if (targetGroup) { if (isMove) { - sourceGroup.moveEditor(control.input, targetGroup); + sourceGroup.moveEditors(editors.map(editor => ({ editor })), targetGroup); } else if (sourceGroup.id !== targetGroup.id) { - sourceGroup.copyEditor(control.input, targetGroup); + sourceGroup.copyEditors(editors.map(editor => ({ editor })), targetGroup); } targetGroup.focus(); } From 2fb0656601fdd8ee16e4bda6f4e788594a22e8f2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2024 10:27:36 +0100 Subject: [PATCH 072/200] Removal of `chat.experimental.offerSetup` (fix microsoft/vscode-copilot#11286) (#236320) --- .../chat/browser/actions/chatActions.ts | 2 - .../contrib/chat/browser/chat.contribution.ts | 7 -- .../browser/chatParticipant.contribution.ts | 5 +- .../contrib/chat/browser/chatSetup.ts | 16 ++-- .../contrib/chat/common/chatContextKeys.ts | 36 ++++---- .../common/gettingStartedContent.ts | 84 ++++++++++--------- 6 files changed, 68 insertions(+), 82 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index de160c8c26315..92f6c42dd3df4 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -534,7 +534,6 @@ MenuRegistry.appendMenuItem(MenuId.CommandCenter, { when: ContextKeyExpr.and( ContextKeyExpr.has('config.chat.commandCenter.enabled'), ContextKeyExpr.or( - ContextKeyExpr.has('config.chat.experimental.offerSetup'), ChatContextKeys.Setup.installed, ChatContextKeys.panelParticipantRegistered ) @@ -552,7 +551,6 @@ registerAction2(class ToggleCopilotControl extends ToggleTitleBarConfigAction { ContextKeyExpr.has('config.window.commandCenter'), ContextKeyExpr.or( ChatContextKeys.Setup.installed, - ContextKeyExpr.has('config.chat.experimental.offerSetup'), ChatContextKeys.panelParticipantRegistered ) ) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 2aa243649de88..b03d4605d160e 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -121,13 +121,6 @@ configurationRegistry.registerConfiguration({ markdownDescription: nls.localize('chat.commandCenter.enabled', "Controls whether the command center shows a menu for actions to control Copilot (requires {0}).", '`#window.commandCenter#`'), default: true }, - 'chat.experimental.offerSetup': { - type: 'boolean', - default: false, - scope: ConfigurationScope.APPLICATION, - 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', scope: ConfigurationScope.APPLICATION, diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts index 355a3d55ff8b3..6326c643d9c73 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts @@ -309,10 +309,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { }, ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.Panel }]), when: ContextKeyExpr.or( - ContextKeyExpr.and( - ContextKeyExpr.has('config.chat.experimental.offerSetup'), - ChatContextKeys.Setup.triggered - ), + ChatContextKeys.Setup.triggered, ChatContextKeys.Setup.installed, ChatContextKeys.panelParticipantRegistered, ChatContextKeys.extensionInvalid diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 54e081fbc0d9b..aab4103ddac9c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -132,12 +132,9 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr private registerActions(): void { const that = this; - const chatSetupTriggerContext = ContextKeyExpr.and( - ContextKeyExpr.has('config.chat.experimental.offerSetup'), - ContextKeyExpr.or( - ChatContextKeys.Setup.installed.negate(), - ChatContextKeys.Setup.canSignUp - ) + const chatSetupTriggerContext = ContextKeyExpr.or( + ChatContextKeys.Setup.installed.negate(), + ChatContextKeys.Setup.canSignUp ); class ChatSetupTriggerAction extends Action2 { @@ -188,10 +185,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr title: ChatSetupHideAction.TITLE, f1: true, category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and( - ChatContextKeys.Setup.installed.negate(), - ContextKeyExpr.has('config.chat.experimental.offerSetup') - ), + precondition: ChatContextKeys.Setup.installed.negate(), menu: { id: MenuId.ChatCommandCenter, group: 'z_hide', @@ -1006,6 +1000,7 @@ class ChatSetupContext extends Disposable { private readonly canSignUpContextKey = ChatContextKeys.Setup.canSignUp.bindTo(this.contextKeyService); private readonly signedOutContextKey = ChatContextKeys.Setup.signedOut.bindTo(this.contextKeyService); private readonly limitedContextKey = ChatContextKeys.Setup.limited.bindTo(this.contextKeyService); + private readonly proContextKey = ChatContextKeys.Setup.pro.bindTo(this.contextKeyService); private readonly triggeredContext = ChatContextKeys.Setup.triggered.bindTo(this.contextKeyService); private readonly installedContext = ChatContextKeys.Setup.installed.bindTo(this.contextKeyService); @@ -1108,6 +1103,7 @@ class ChatSetupContext extends Disposable { this.signedOutContextKey.set(this._state.entitlement === ChatEntitlement.Unknown); this.canSignUpContextKey.set(this._state.entitlement === ChatEntitlement.Available); this.limitedContextKey.set(this._state.entitlement === ChatEntitlement.Limited); + this.proContextKey.set(this._state.entitlement === ChatEntitlement.Pro); this.triggeredContext.set(!!this._state.triggered); this.installedContext.set(!!this._state.installed); diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 047a5824af2fd..f615233de5050 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -42,31 +42,31 @@ export namespace ChatContextKeys { 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 = { - canSignUp: new RawContextKey('chatSetupCanSignUp', false, true), // True when user can sign up to be a chat limited user. + // State signedOut: new RawContextKey('chatSetupSignedOut', false, true), // True when user is signed out. - limited: new RawContextKey('chatSetupLimited', false, true), // True when user is a chat limited user. - triggered: new RawContextKey('chatSetupTriggered', false, true), // True when chat setup is triggered. installed: new RawContextKey('chatSetupInstalled', false, true), // True when the chat extension is installed. + + // Plans + canSignUp: new RawContextKey('chatPlanCanSignUp', false, true), // True when user can sign up to be a chat limited user. + limited: new RawContextKey('chatPlanLimited', false, true), // True when user is a chat limited user. + pro: new RawContextKey('chatPlanPro', false, true) // True when user is a chat pro user. }; export const SetupViewKeys = new Set([ChatContextKeys.Setup.triggered.key, ChatContextKeys.Setup.installed.key, ChatContextKeys.Setup.signedOut.key, ChatContextKeys.Setup.canSignUp.key]); - export const SetupViewCondition = ContextKeyExpr.and( - ContextKeyExpr.has('config.chat.experimental.offerSetup'), - ContextKeyExpr.or( - ContextKeyExpr.and( - ChatContextKeys.Setup.triggered, - ChatContextKeys.Setup.installed.negate() - ), - ContextKeyExpr.and( - ChatContextKeys.Setup.canSignUp, - ChatContextKeys.Setup.installed - ), - ContextKeyExpr.and( - ChatContextKeys.Setup.signedOut, - ChatContextKeys.Setup.installed - ) + export const SetupViewCondition = ContextKeyExpr.or( + ContextKeyExpr.and( + ChatContextKeys.Setup.triggered, + ChatContextKeys.Setup.installed.negate() + ), + ContextKeyExpr.and( + ChatContextKeys.Setup.canSignUp, + ChatContextKeys.Setup.installed + ), + ContextKeyExpr.and( + ChatContextKeys.Setup.signedOut, + ChatContextKeys.Setup.installed ) )!; diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts index 303905c913603..7fd3e30163f35 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts @@ -246,9 +246,9 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ content: { type: 'steps', steps: [ - createCopilotSetupStep('CopilotSetupSignedOut', CopilotSignedOutButton, 'config.chat.experimental.offerSetup && chatSetupSignedOut', true), - createCopilotSetupStep('CopilotSetupComplete', CopilotCompleteButton, 'config.chat.experimental.offerSetup && chatSetupInstalled && (chatSetupEntitled || chatSetupLimited)', false), - createCopilotSetupStep('CopilotSetupSignedIn', CopilotSignedInButton, 'config.chat.experimental.offerSetup && !chatSetupSignedOut && (!chatSetupInstalled || chatSetupCanSignUp)', true), + createCopilotSetupStep('CopilotSetupSignedOut', CopilotSignedOutButton, 'chatSetupSignedOut', true), + createCopilotSetupStep('CopilotSetupComplete', CopilotCompleteButton, 'chatSetupInstalled && (chatPlanPro || chatPlanLimited)', false), + createCopilotSetupStep('CopilotSetupSignedIn', CopilotSignedInButton, '!chatSetupSignedOut && (!chatSetupInstalled || chatPlanCanSignUp)', true), { id: 'pickColorTheme', title: localize('gettingStarted.pickColor.title', "Choose your theme"), @@ -277,30 +277,31 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ type: 'svg', altText: 'Language extensions', path: 'languages.svg' }, }, - { - id: 'settings', - title: localize('gettingStarted.settings.title', "Tune your settings"), - description: localize('gettingStarted.settings.description.interpolated', "Customize every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), - when: '!config.chat.experimental.offerSetup', - media: { - type: 'svg', altText: 'VS Code Settings', path: 'settings.svg' - }, - }, - { - id: 'settingsSync', - title: localize('gettingStarted.settingsSync.title', "Sync settings across devices"), - description: localize('gettingStarted.settingsSync.description.interpolated', "Keep your essential customizations backed up and updated across all your devices.\n{0}", Button(localize('enableSync', "Backup and Sync Settings"), 'command:workbench.userDataSync.actions.turnOn')), - when: '!config.chat.experimental.offerSetup && syncStatus != uninitialized', - completionEvents: ['onEvent:sync-enabled'], - media: { - type: 'svg', altText: 'The "Turn on Sync" entry in the settings gear menu.', path: 'settingsSync.svg' - }, - }, + // Hidden in favor of copilot entry (to be revisited when copilot entry moves, if at all) + // { + // id: 'settings', + // title: localize('gettingStarted.settings.title', "Tune your settings"), + // description: localize('gettingStarted.settings.description.interpolated', "Customize every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), + // when: '!config.chat.experimental.offerSetup', + // media: { + // type: 'svg', altText: 'VS Code Settings', path: 'settings.svg' + // }, + // }, + // { + // id: 'settingsSync', + // title: localize('gettingStarted.settingsSync.title', "Sync settings across devices"), + // description: localize('gettingStarted.settingsSync.description.interpolated', "Keep your essential customizations backed up and updated across all your devices.\n{0}", Button(localize('enableSync', "Backup and Sync Settings"), 'command:workbench.userDataSync.actions.turnOn')), + // when: '!config.chat.experimental.offerSetup && syncStatus != uninitialized', + // completionEvents: ['onEvent:sync-enabled'], + // media: { + // type: 'svg', altText: 'The "Turn on Sync" entry in the settings gear menu.', path: 'settingsSync.svg' + // }, + // }, { id: 'settingsAndSync', title: localize('gettingStarted.settings.title', "Tune your settings"), description: localize('gettingStarted.settingsAndSync.description.interpolated', "Customize every aspect of VS Code and your extensions to your liking. [Back up and sync](command:workbench.userDataSync.actions.turnOn) your essential customizations across all your devices.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), - when: 'config.chat.experimental.offerSetup && syncStatus != uninitialized', + when: 'syncStatus != uninitialized', completionEvents: ['onEvent:sync-enabled'], media: { type: 'svg', altText: 'VS Code Settings', path: 'settings.svg' @@ -312,24 +313,25 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ description: localize('gettingStarted.commandPalette.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in VS Code.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), media: { type: 'svg', altText: 'Command Palette overlay for searching and executing commands.', path: 'commandPalette.svg' }, }, - { - id: 'pickAFolderTask-Mac', - title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), - description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFileFolder')), - when: '!config.chat.experimental.offerSetup && isMac && workspaceFolderCount == 0', - media: { - type: 'svg', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: 'openFolder.svg' - } - }, - { - id: 'pickAFolderTask-Other', - title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), - description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFolder')), - when: '!config.chat.experimental.offerSetup && !isMac && workspaceFolderCount == 0', - media: { - type: 'svg', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: 'openFolder.svg' - } - }, + // Hidden in favor of copilot entry (to be revisited when copilot entry moves, if at all) + // { + // id: 'pickAFolderTask-Mac', + // title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), + // description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFileFolder')), + // when: '!config.chat.experimental.offerSetup && isMac && workspaceFolderCount == 0', + // media: { + // type: 'svg', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: 'openFolder.svg' + // } + // }, + // { + // id: 'pickAFolderTask-Other', + // title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), + // description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFolder')), + // when: '!config.chat.experimental.offerSetup && !isMac && workspaceFolderCount == 0', + // media: { + // type: 'svg', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: 'openFolder.svg' + // } + // }, { id: 'quickOpen', title: localize('gettingStarted.quickOpen.title', "Quickly navigate between your files"), From 3daa33e93a945f4f6cab577f424e1862c00846c1 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:31:29 +0100 Subject: [PATCH 073/200] Restore close commands for keybinding support (#236446) * bring back the close commands * improve discoverability --- .../browser/actions/layoutActions.ts | 22 +++---------------- .../parts/auxiliarybar/auxiliaryBarActions.ts | 18 +++++++++++++++ .../browser/parts/panel/panelActions.ts | 18 +++++++++++++++ .../browser/parts/sidebar/sidebarActions.ts | 18 +++++++++++++++ 4 files changed, 57 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index c3ba2ab9907d9..75253e235de85 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -53,25 +53,6 @@ const fullscreenIcon = registerIcon('fullscreen', Codicon.screenFull, localize(' const centerLayoutIcon = registerIcon('centerLayoutIcon', Codicon.layoutCentered, localize('centerLayoutIcon', "Represents centered layout mode")); const zenModeIcon = registerIcon('zenMode', Codicon.target, localize('zenModeIcon', "Represents zen mode")); - -// --- Close Side Bar - -registerAction2(class extends Action2 { - - constructor() { - super({ - id: 'workbench.action.closeSidebar', - title: localize2('closeSidebar', 'Close Primary Side Bar'), - category: Categories.View, - f1: true - }); - } - - run(accessor: ServicesAccessor): void { - accessor.get(IWorkbenchLayoutService).setPartHidden(true, Parts.SIDEBAR_PART); - } -}); - export const ToggleActivityBarVisibilityActionId = 'workbench.action.toggleActivityBarVisibility'; // --- Toggle Centered Layout @@ -323,6 +304,9 @@ class ToggleSidebarVisibilityAction extends Action2 { title: localize('primary sidebar', "Primary Side Bar"), mnemonicTitle: localize({ key: 'primary sidebar mnemonic', comment: ['&& denotes a mnemonic'] }, "&&Primary Side Bar"), }, + metadata: { + description: localize('openAndCloseSidebar', 'Open/Show and Close/Hide Sidebar'), + }, category: Categories.View, f1: true, keybinding: { diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts index 3bea858332e9e..e564a8d49d778 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts @@ -41,6 +41,9 @@ export class ToggleAuxiliaryBarAction extends Action2 { }, icon: closeIcon, // Ensures no flickering when using toggled.icon category: Categories.View, + metadata: { + description: localize('openAndCloseAuxiliaryBar', 'Open/Show and Close/Hide Secondary Side Bar'), + }, f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -74,6 +77,21 @@ export class ToggleAuxiliaryBarAction extends Action2 { registerAction2(ToggleAuxiliaryBarAction); +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.closeAuxiliaryBar', + title: localize2('closeSecondarySideBar', 'Hide Secondary Side Bar'), + category: Categories.View, + precondition: AuxiliaryBarVisibleContext, + f1: true, + }); + } + run(accessor: ServicesAccessor) { + accessor.get(IWorkbenchLayoutService).setPartHidden(true, Parts.AUXILIARYBAR_PART); + } +}); + registerAction2(class FocusAuxiliaryBarAction extends Action2 { static readonly ID = 'workbench.action.focusAuxiliaryBar'; diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index e8fbbacb68a4f..b3be54f49d789 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -46,6 +46,9 @@ export class TogglePanelAction extends Action2 { icon: closeIcon, // Ensures no flickering when using toggled.icon f1: true, category: Categories.View, + metadata: { + description: localize('openAndClosePanel', 'Open/Show and Close/Hide Panel'), + }, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyJ, weight: KeybindingWeight.WorkbenchContrib }, menu: [ { @@ -73,6 +76,21 @@ export class TogglePanelAction extends Action2 { registerAction2(TogglePanelAction); +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.closePanel', + title: localize2('closePanel', 'Hide Panel'), + category: Categories.View, + precondition: PanelVisibleContext, + f1: true, + }); + } + run(accessor: ServicesAccessor) { + accessor.get(IWorkbenchLayoutService).setPartHidden(true, Parts.PANEL_PART); + } +}); + registerAction2(class extends Action2 { static readonly ID = 'workbench.action.focusPanel'; diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarActions.ts b/src/vs/workbench/browser/parts/sidebar/sidebarActions.ts index 408bb3e4eea05..4256ecb53524a 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarActions.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarActions.ts @@ -13,6 +13,24 @@ import { KeybindingWeight } from '../../../../platform/keybinding/common/keybind import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js'; import { ViewContainerLocation } from '../../../common/views.js'; +import { SideBarVisibleContext } from '../../../common/contextkeys.js'; + +registerAction2(class extends Action2 { + + constructor() { + super({ + id: 'workbench.action.closeSidebar', + title: localize2('closeSidebar', 'Close Primary Side Bar'), + category: Categories.View, + f1: true, + precondition: SideBarVisibleContext + }); + } + + run(accessor: ServicesAccessor): void { + accessor.get(IWorkbenchLayoutService).setPartHidden(true, Parts.SIDEBAR_PART); + } +}); export class FocusSideBarAction extends Action2 { From 224ade93d0510c71cbd362e7a8974b2e3df471d6 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:35:16 +0100 Subject: [PATCH 074/200] Git - don't use look-behind regex (#236447) --- extensions/git/src/git.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 4a968792a7726..f2b9100d79a14 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -502,7 +502,7 @@ export class Git { const repoUri = Uri.file(repositoryRootPath); const pathUri = Uri.file(pathInsidePossibleRepository); if (repoUri.authority.length !== 0 && pathUri.authority.length === 0) { - const match = /(?<=^\/?)([a-zA-Z])(?=:\/)/.exec(pathUri.path); + const match = /^[\/]?([a-zA-Z])[:\/]/.exec(pathUri.path); if (match !== null) { const [, letter] = match; From 29a39607d85dba549907a12b4ceece574d6dce51 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:40:30 +0100 Subject: [PATCH 075/200] Align panel badge style with activity bar (#236448) closes #195998 --- src/vs/workbench/browser/parts/panel/panelPart.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 6363f26638469..d93cfd11b3dee 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -16,7 +16,7 @@ import { IInstantiationService } from '../../../../platform/instantiation/common import { TogglePanelAction } from './panelActions.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND } from '../../../common/theme.js'; -import { badgeBackground, badgeForeground, contrastBorder } from '../../../../platform/theme/common/colorRegistry.js'; +import { contrastBorder } from '../../../../platform/theme/common/colorRegistry.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { Dimension } from '../../../../base/browser/dom.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -130,13 +130,12 @@ export class PanelPart extends AbstractPaneCompositePart { } protected getCompositeBarOptions(): IPaneCompositeBarOptions { - const showIcons = this.configurationService.getValue('workbench.panel.showLabels') === false; return { partContainerClass: 'panel', pinnedViewContainersKey: 'workbench.panel.pinnedPanels', placeholderViewContainersKey: 'workbench.panel.placeholderPanels', viewContainersWorkspaceStateKey: 'workbench.panel.viewContainersWorkspaceState', - icon: showIcons, + icon: this.configurationService.getValue('workbench.panel.showLabels') === false, orientation: ActionsOrientation.HORIZONTAL, recomputeSizes: true, activityHoverOptions: { @@ -153,8 +152,8 @@ export class PanelPart extends AbstractPaneCompositePart { activeBorderBottomColor: theme.getColor(PANEL_ACTIVE_TITLE_BORDER), activeForegroundColor: theme.getColor(PANEL_ACTIVE_TITLE_FOREGROUND), inactiveForegroundColor: theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND), - badgeBackground: theme.getColor(showIcons ? ACTIVITY_BAR_BADGE_BACKGROUND : badgeBackground), - badgeForeground: theme.getColor(showIcons ? ACTIVITY_BAR_BADGE_FOREGROUND : badgeForeground), + badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), + badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), dragAndDropBorder: theme.getColor(PANEL_DRAG_AND_DROP_BORDER) }) }; From 777fd07cccc3de449e529c9f701c2cfdd36ecb3e Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:20:00 +0100 Subject: [PATCH 076/200] Git - adopt #private in Git extension API (#236444) * Git - adopt #private in Git extension API * Fix post commit command provider --- extensions/git/src/api/api1.ts | 236 +++++++++++++---------- extensions/git/src/main.ts | 2 +- extensions/git/src/postCommitCommands.ts | 14 +- 3 files changed, 142 insertions(+), 110 deletions(-) diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index a8a8fb694b16e..8cc0b99f11329 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +/* eslint-disable local/code-no-native-private */ + 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'; @@ -15,222 +17,242 @@ 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) { } + #inputBox: SourceControlInputBox; + + constructor(inputBox: SourceControlInputBox) { this.#inputBox = inputBox; } + + set value(value: string) { this.#inputBox.value = value; } + get value(): string { return this.#inputBox.value; } } export class ApiChange implements Change { + #resource: Resource; + constructor(resource: Resource) { this.#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) { } + 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; } } export class ApiRepositoryState implements RepositoryState { + #repository: BaseRepository; + readonly onDidChange: Event; - get HEAD(): Branch | undefined { return this._repository.HEAD; } + constructor(repository: BaseRepository) { + this.#repository = repository; + this.onDidChange = this.#repository.onDidRunGitStatus; + } + + 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)); } - - readonly onDidChange: Event = this._repository.onDidRunGitStatus; - - constructor(private _repository: BaseRepository) { } + 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)); } } export class ApiRepositoryUIState implements RepositoryUIState { + #sourceControl: SourceControl; + readonly onDidChange: Event; - get selected(): boolean { return this._sourceControl.selected; } - - readonly onDidChange: Event = mapEvent(this._sourceControl.onDidChangeSelection, () => null); + constructor(sourceControl: SourceControl) { + this.#sourceControl = sourceControl; + this.onDidChange = mapEvent(this.#sourceControl.onDidChangeSelection, () => null); + } - constructor(private _sourceControl: SourceControl) { } + get selected(): boolean { return this.#sourceControl.selected; } } 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); + #repository: BaseRepository; + + readonly rootUri: Uri; + readonly inputBox: InputBox; + readonly state: RepositoryState; + readonly ui: RepositoryUIState; - readonly onDidCommit: Event = mapEvent( - filterEvent(this.repository.onDidRunOperation, e => e.operation.kind === OperationKind.Commit), () => null); + readonly onDidCommit: Event; + readonly onDidCheckout: Event; - readonly onDidCheckout: Event = mapEvent( - filterEvent(this.repository.onDidRunOperation, e => e.operation.kind === OperationKind.Checkout || e.operation.kind === OperationKind.CheckoutTracking), () => null); + constructor(repository: BaseRepository) { + this.#repository = repository; - constructor(readonly repository: BaseRepository) { } + this.rootUri = Uri.file(this.#repository.root); + this.inputBox = new ApiInputBox(this.#repository.inputBox); + this.state = new ApiRepositoryState(this.#repository); + this.ui = new ApiRepositoryUIState(this.#repository.sourceControl); + + this.onDidCommit = mapEvent( + filterEvent(this.#repository.onDidRunOperation, e => e.operation.kind === OperationKind.Commit), () => null); + this.onDidCheckout = mapEvent( + filterEvent(this.#repository.onDidRunOperation, e => e.operation.kind === OperationKind.Checkout || e.operation.kind === OperationKind.CheckoutTracking), () => null); + } apply(patch: string, reverse?: boolean): Promise { - return this.repository.apply(patch, reverse); + return this.#repository.apply(patch, reverse); } getConfigs(): Promise<{ key: string; value: string }[]> { - return this.repository.getConfigs(); + return this.#repository.getConfigs(); } getConfig(key: string): Promise { - return this.repository.getConfig(key); + return this.#repository.getConfig(key); } setConfig(key: string, value: string): Promise { - return this.repository.setConfig(key, value); + return this.#repository.setConfig(key, value); } getGlobalConfig(key: string): Promise { - return this.repository.getGlobalConfig(key); + return this.#repository.getGlobalConfig(key); } getObjectDetails(treeish: string, path: string): Promise<{ mode: string; object: string; size: number }> { - return this.repository.getObjectDetails(treeish, path); + return this.#repository.getObjectDetails(treeish, path); } detectObjectType(object: string): Promise<{ mimetype: string; encoding?: string }> { - return this.repository.detectObjectType(object); + return this.#repository.detectObjectType(object); } buffer(ref: string, filePath: string): Promise { - return this.repository.buffer(ref, filePath); + return this.#repository.buffer(ref, filePath); } show(ref: string, path: string): Promise { - return this.repository.show(ref, path); + return this.#repository.show(ref, path); } getCommit(ref: string): Promise { - return this.repository.getCommit(ref); + 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); + return this.#repository.diff(cached); } diffWithHEAD(): Promise; diffWithHEAD(path: string): Promise; diffWithHEAD(path?: string): Promise { - return this.repository.diffWithHEAD(path); + 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); + return this.#repository.diffWith(ref, path); } diffIndexWithHEAD(): Promise; diffIndexWithHEAD(path: string): Promise; diffIndexWithHEAD(path?: string): Promise { - return this.repository.diffIndexWithHEAD(path); + 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); + return this.#repository.diffIndexWith(ref, path); } diffBlobs(object1: string, object2: string): Promise { - return this.repository.diffBlobs(object1, object2); + 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 { - return this.repository.diffBetween(ref1, ref2, path); + return this.#repository.diffBetween(ref1, ref2, path); } hashObject(data: string): Promise { - return this.repository.hashObject(data); + return this.#repository.hashObject(data); } createBranch(name: string, checkout: boolean, ref?: string | undefined): Promise { - return this.repository.branch(name, checkout, ref); + return this.#repository.branch(name, checkout, ref); } deleteBranch(name: string, force?: boolean): Promise { - return this.repository.deleteBranch(name, force); + return this.#repository.deleteBranch(name, force); } getBranch(name: string): Promise { - return this.repository.getBranch(name); + return this.#repository.getBranch(name); } getBranches(query: BranchQuery, cancellationToken?: CancellationToken): Promise { - return this.repository.getBranches(query, cancellationToken); + return this.#repository.getBranches(query, cancellationToken); } getBranchBase(name: string): Promise { - return this.repository.getBranchBase(name); + return this.#repository.getBranchBase(name); } setBranchUpstream(name: string, upstream: string): Promise { - return this.repository.setBranchUpstream(name, upstream); + return this.#repository.setBranchUpstream(name, upstream); } getRefs(query: RefQuery, cancellationToken?: CancellationToken): Promise { - return this.repository.getRefs(query, cancellationToken); + return this.#repository.getRefs(query, cancellationToken); } checkIgnore(paths: string[]): Promise> { - return this.repository.checkIgnore(paths); + return this.#repository.checkIgnore(paths); } getMergeBase(ref1: string, ref2: string): Promise { - return this.repository.getMergeBase(ref1, ref2); + return this.#repository.getMergeBase(ref1, ref2); } tag(name: string, message: string, ref?: string | undefined): Promise { - return this.repository.tag({ name, message, ref }); + return this.#repository.tag({ name, message, ref }); } deleteTag(name: string): Promise { - return this.repository.deleteTag(name); + return this.#repository.deleteTag(name); } status(): Promise { - return this.repository.status(); + return this.#repository.status(); } checkout(treeish: string): Promise { - return this.repository.checkout(treeish); + return this.#repository.checkout(treeish); } addRemote(name: string, url: string): Promise { - return this.repository.addRemote(name, url); + return this.#repository.addRemote(name, url); } removeRemote(name: string): Promise { - return this.repository.removeRemote(name); + return this.#repository.removeRemote(name); } renameRemote(name: string, newName: string): Promise { - return this.repository.renameRemote(name, newName); + return this.#repository.renameRemote(name, newName); } fetch(arg0?: FetchOptions | string | undefined, @@ -239,86 +261,92 @@ export class ApiRepository implements Repository { prune?: boolean | undefined ): Promise { if (arg0 !== undefined && typeof arg0 !== 'string') { - return this.repository.fetch(arg0); + return this.#repository.fetch(arg0); } - return this.repository.fetch({ remote: arg0, ref, depth, prune }); + return this.#repository.fetch({ remote: arg0, ref, depth, prune }); } pull(unshallow?: boolean): Promise { - return this.repository.pull(undefined, unshallow); + 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); + return this.#repository.pushTo(remoteName, branchName, setUpstream, force); } blame(path: string): Promise { - return this.repository.blame(path); + return this.#repository.blame(path); } log(options?: LogOptions): Promise { - return this.repository.log(options); + 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); + return this.#repository.merge(ref); } mergeAbort(): Promise { - return this.repository.mergeAbort(); + return this.#repository.mergeAbort(); } applyStash(index?: number): Promise { - return this.repository.applyStash(index); + return this.#repository.applyStash(index); } popStash(index?: number): Promise { - return this.repository.popStash(index); + return this.#repository.popStash(index); } dropStash(index?: number): Promise { - return this.repository.dropStash(index); + return this.#repository.dropStash(index); } } export class ApiGit implements Git { + #model: Model; - get path(): string { return this._model.git.path; } + constructor(model: Model) { this.#model = model; } - constructor(private _model: Model) { } + get path(): string { return this.#model.git.path; } } export class ApiImpl implements API { + #model: Model; + readonly git: ApiGit; - readonly git = new ApiGit(this._model); + constructor(model: Model) { + this.#model = model; + this.git = new ApiGit(this.#model); + } get state(): APIState { - return this._model.state; + return this.#model.state; } get onDidChangeState(): Event { - return this._model.onDidChangeState; + return this.#model.onDidChangeState; } get onDidPublish(): Event { - return this._model.onDidPublish; + 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 { @@ -326,14 +354,14 @@ export class ApiImpl implements API { } getRepository(uri: Uri): Repository | null { - const result = this._model.getRepository(uri); + 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); + await this.#model.git.init(path, options); + await this.#model.openRepository(path); return this.getRepository(root) || null; } @@ -342,7 +370,7 @@ export class ApiImpl implements API { return null; } - await this._model.openRepository(root.fsPath); + await this.#model.openRepository(root.fsPath); return this.getRepository(root) || null; } @@ -350,7 +378,7 @@ export class ApiImpl implements API { 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)); @@ -358,26 +386,24 @@ export class ApiImpl implements API { } registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable { - return this._model.registerRemoteSourcePublisher(publisher); + return this.#model.registerRemoteSourcePublisher(publisher); } registerCredentialsProvider(provider: CredentialsProvider): Disposable { - return this._model.registerCredentialsProvider(provider); + return this.#model.registerCredentialsProvider(provider); } registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable { - return this._model.registerPostCommitCommandsProvider(provider); + return this.#model.registerPostCommitCommandsProvider(provider); } registerPushErrorHandler(handler: PushErrorHandler): Disposable { - return this._model.registerPushErrorHandler(handler); + return this.#model.registerPushErrorHandler(handler); } registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable { - return this._model.registerBranchProtectionProvider(root, provider); + return this.#model.registerBranchProtectionProvider(root, provider); } - - constructor(private _model: Model) { } } function getRefType(type: RefType): string { diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 7180890ad846f..515f57c12cf18 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -121,7 +121,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, new TerminalShellExecutionManager(model, logger) ); - const postCommitCommandsProvider = new GitPostCommitCommandsProvider(); + const postCommitCommandsProvider = new GitPostCommitCommandsProvider(model); model.registerPostCommitCommandsProvider(postCommitCommandsProvider); const diagnosticsManager = new GitCommitInputBoxDiagnosticsManager(model); diff --git a/extensions/git/src/postCommitCommands.ts b/extensions/git/src/postCommitCommands.ts index d4e227b6db76f..69a18114a41e2 100644 --- a/extensions/git/src/postCommitCommands.ts +++ b/extensions/git/src/postCommitCommands.ts @@ -5,7 +5,7 @@ import { Command, commands, Disposable, Event, EventEmitter, Memento, Uri, workspace, l10n } from 'vscode'; import { PostCommitCommandsProvider } from './api/git'; -import { Repository } from './repository'; +import { IRepositoryResolver, Repository } from './repository'; import { ApiRepository } from './api/api1'; import { dispose } from './util'; import { OperationKind } from './operation'; @@ -18,17 +18,23 @@ export interface IPostCommitCommandsProviderRegistry { } export class GitPostCommitCommandsProvider implements PostCommitCommandsProvider { + constructor(private readonly _repositoryResolver: IRepositoryResolver) { } + getCommands(apiRepository: ApiRepository): Command[] { - const config = workspace.getConfiguration('git', Uri.file(apiRepository.repository.root)); + const repository = this._repositoryResolver.getRepository(apiRepository.rootUri); + if (!repository) { + return []; + } + + const config = workspace.getConfiguration('git', Uri.file(repository.root)); // Branch protection - const isBranchProtected = apiRepository.repository.isBranchProtected(); + const isBranchProtected = repository.isBranchProtected(); const branchProtectionPrompt = config.get<'alwaysCommit' | 'alwaysCommitToNewBranch' | 'alwaysPrompt'>('branchProtectionPrompt')!; const alwaysPrompt = isBranchProtected && branchProtectionPrompt === 'alwaysPrompt'; const alwaysCommitToNewBranch = isBranchProtected && branchProtectionPrompt === 'alwaysCommitToNewBranch'; // Icon - const repository = apiRepository.repository; const isCommitInProgress = repository.operations.isRunning(OperationKind.Commit) || repository.operations.isRunning(OperationKind.PostCommitCommand); const icon = isCommitInProgress ? '$(sync~spin)' : alwaysPrompt ? '$(lock)' : alwaysCommitToNewBranch ? '$(git-branch)' : undefined; From 9fb1d574701dd41b6e5014e1c20f3cc7c4e4f353 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2024 11:34:24 +0100 Subject: [PATCH 077/200] Reapply "debt: clean up obsolete file usage" (#236453) * Reapply "debt: clean up obsolete file usage" (#236433) This reverts commit cf2ebd91b8e42e3bc5ab0e85e3323c886a977ffe. * Fix extension deletion logic to ensure proper removal and handle file not found errors --- .../common/extensionManagement.ts | 4 +- .../common/extensionsScannerService.ts | 51 ++---- .../node/extensionManagementService.ts | 171 +++++++++--------- .../node/extensionsWatcher.ts | 20 +- .../node/extensionsScannerService.test.ts | 33 +--- 5 files changed, 122 insertions(+), 157 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 155079831fcc7..f08b46ae65b30 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -456,8 +456,8 @@ export const enum ExtensionManagementErrorCode { Extract = 'Extract', Scanning = 'Scanning', ScanningExtension = 'ScanningExtension', - ReadUninstalled = 'ReadUninstalled', - UnsetUninstalled = 'UnsetUninstalled', + ReadRemoved = 'ReadRemoved', + UnsetRemoved = 'UnsetRemoved', Delete = 'Delete', Rename = 'Rename', IntializeDefaultProfile = 'IntializeDefaultProfile', diff --git a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts index 99868cddb5d63..a186b6fa04567 100644 --- a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts @@ -7,7 +7,6 @@ import { coalesce } from '../../../base/common/arrays.js'; import { ThrottledDelayer } from '../../../base/common/async.js'; import * as objects from '../../../base/common/objects.js'; import { VSBuffer } from '../../../base/common/buffer.js'; -import { IStringDictionary } from '../../../base/common/collections.js'; import { getErrorMessage } from '../../../base/common/errors.js'; import { getNodeType, parse, ParseError } from '../../../base/common/json.js'; import { getParseErrorMessage } from '../../../base/common/jsonErrorMessages.js'; @@ -18,12 +17,11 @@ import * as platform from '../../../base/common/platform.js'; import { basename, isEqual, joinPath } from '../../../base/common/resources.js'; import * as semver from '../../../base/common/semver/semver.js'; import Severity from '../../../base/common/severity.js'; -import { isEmptyObject } from '../../../base/common/types.js'; import { URI } from '../../../base/common/uri.js'; import { localize } from '../../../nls.js'; import { IEnvironmentService } from '../../environment/common/environment.js'; import { IProductVersion, Metadata } from './extensionManagement.js'; -import { areSameExtensions, computeTargetPlatform, ExtensionKey, getExtensionId, getGalleryExtensionId } from './extensionManagementUtil.js'; +import { areSameExtensions, computeTargetPlatform, getExtensionId, getGalleryExtensionId } from './extensionManagementUtil.js'; import { ExtensionType, ExtensionIdentifier, IExtensionManifest, TargetPlatform, IExtensionIdentifier, IRelaxedExtensionManifest, UNDEFINED_PUBLISHER, IExtensionDescription, BUILTIN_MANIFEST_CACHE_FILE, USER_MANIFEST_CACHE_FILE, ExtensionIdentifierMap, parseEnabledApiProposalNames } from '../../extensions/common/extensions.js'; import { validateExtensionManifest } from '../../extensions/common/extensionValidator.js'; import { FileOperationResult, IFileService, toFileOperationResult } from '../../files/common/files.js'; @@ -106,7 +104,6 @@ export type ScanOptions = { readonly profileLocation?: URI; readonly includeInvalid?: boolean; readonly includeAllVersions?: boolean; - readonly includeUninstalled?: boolean; readonly checkControlFile?: boolean; readonly language?: string; readonly useCache?: boolean; @@ -145,10 +142,9 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem private readonly _onDidChangeCache = this._register(new Emitter()); readonly onDidChangeCache = this._onDidChangeCache.event; - private readonly obsoleteFile = joinPath(this.userExtensionsLocation, '.obsolete'); - private readonly systemExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, this.currentProfile, this.obsoleteFile)); - private readonly userExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, this.currentProfile, this.obsoleteFile)); - private readonly extensionsScanner = this._register(this.instantiationService.createInstance(ExtensionsScanner, this.obsoleteFile)); + private readonly systemExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, this.currentProfile)); + private readonly userExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, this.currentProfile)); + private readonly extensionsScanner = this._register(this.instantiationService.createInstance(ExtensionsScanner)); constructor( readonly systemExtensionsLocation: URI, @@ -199,8 +195,8 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem const location = scanOptions.profileLocation ?? this.userExtensionsLocation; this.logService.trace('Started scanning user extensions', location); const profileScanOptions: IProfileExtensionsScanOptions | undefined = this.uriIdentityService.extUri.isEqual(scanOptions.profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource) ? { bailOutWhenFileNotFound: true } : undefined; - const extensionsScannerInput = await this.createExtensionScannerInput(location, !!scanOptions.profileLocation, ExtensionType.User, !scanOptions.includeUninstalled, scanOptions.language, true, profileScanOptions, scanOptions.productVersion ?? this.getProductVersion()); - const extensionsScanner = scanOptions.useCache && !extensionsScannerInput.devMode && extensionsScannerInput.excludeObsolete ? this.userExtensionsCachedScanner : this.extensionsScanner; + const extensionsScannerInput = await this.createExtensionScannerInput(location, !!scanOptions.profileLocation, ExtensionType.User, scanOptions.language, true, profileScanOptions, scanOptions.productVersion ?? this.getProductVersion()); + const extensionsScanner = scanOptions.useCache && !extensionsScannerInput.devMode ? this.userExtensionsCachedScanner : this.extensionsScanner; let extensions: IRelaxedScannedExtension[]; try { extensions = await extensionsScanner.scanExtensions(extensionsScannerInput); @@ -221,7 +217,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem if (this.environmentService.isExtensionDevelopment && this.environmentService.extensionDevelopmentLocationURI) { const extensions = (await Promise.all(this.environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file) .map(async extensionDevelopmentLocationURI => { - const input = await this.createExtensionScannerInput(extensionDevelopmentLocationURI, false, ExtensionType.User, true, scanOptions.language, false /* do not validate */, undefined, scanOptions.productVersion ?? this.getProductVersion()); + const input = await this.createExtensionScannerInput(extensionDevelopmentLocationURI, false, ExtensionType.User, scanOptions.language, false /* do not validate */, undefined, scanOptions.productVersion ?? this.getProductVersion()); const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(input); return extensions.map(extension => { // Override the extension type from the existing extensions @@ -237,7 +233,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } async scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise { - const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, true, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion()); + const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion()); const extension = await this.extensionsScanner.scanExtension(extensionsScannerInput); if (!extension) { return null; @@ -249,7 +245,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } async scanOneOrMultipleExtensions(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise { - const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, true, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion()); + const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion()); const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(extensionsScannerInput); return this.applyScanOptions(extensions, extensionType, scanOptions, true); } @@ -405,7 +401,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem private async scanDefaultSystemExtensions(useCache: boolean, language: string | undefined): Promise { this.logService.trace('Started scanning system extensions'); - const extensionsScannerInput = await this.createExtensionScannerInput(this.systemExtensionsLocation, false, ExtensionType.System, true, language, true, undefined, this.getProductVersion()); + const extensionsScannerInput = await this.createExtensionScannerInput(this.systemExtensionsLocation, false, ExtensionType.System, language, true, undefined, this.getProductVersion()); const extensionsScanner = useCache && !extensionsScannerInput.devMode ? this.systemExtensionsCachedScanner : this.extensionsScanner; const result = await extensionsScanner.scanExtensions(extensionsScannerInput); this.logService.trace('Scanned system extensions:', result.length); @@ -435,7 +431,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem break; } } - const result = await Promise.all(devSystemExtensionsLocations.map(async location => this.extensionsScanner.scanExtension((await this.createExtensionScannerInput(location, false, ExtensionType.System, true, language, true, undefined, this.getProductVersion()))))); + const result = await Promise.all(devSystemExtensionsLocations.map(async location => this.extensionsScanner.scanExtension((await this.createExtensionScannerInput(location, false, ExtensionType.System, language, true, undefined, this.getProductVersion()))))); this.logService.trace('Scanned dev system extensions:', result.length); return coalesce(result); } @@ -449,7 +445,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } } - private async createExtensionScannerInput(location: URI, profile: boolean, type: ExtensionType, excludeObsolete: boolean, language: string | undefined, validate: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined, productVersion: IProductVersion): Promise { + private async createExtensionScannerInput(location: URI, profile: boolean, type: ExtensionType, language: string | undefined, validate: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined, productVersion: IProductVersion): Promise { const translations = await this.getTranslations(language ?? platform.language); const mtime = await this.getMtime(location); const applicationExtensionsLocation = profile && !this.uriIdentityService.extUri.isEqual(location, this.userDataProfilesService.defaultProfile.extensionsResource) ? this.userDataProfilesService.defaultProfile.extensionsResource : undefined; @@ -462,7 +458,6 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem profile, profileScanOptions, type, - excludeObsolete, validate, productVersion.version, productVersion.date, @@ -504,7 +499,6 @@ export class ExtensionScannerInput { public readonly profile: boolean, public readonly profileScanOptions: IProfileExtensionsScanOptions | undefined, public readonly type: ExtensionType, - public readonly excludeObsolete: boolean, public readonly validate: boolean, public readonly productVersion: string, public readonly productDate: string | undefined, @@ -534,7 +528,6 @@ export class ExtensionScannerInput { && a.profile === b.profile && objects.equals(a.profileScanOptions, b.profileScanOptions) && a.type === b.type - && a.excludeObsolete === b.excludeObsolete && a.validate === b.validate && a.productVersion === b.productVersion && a.productDate === b.productDate @@ -558,7 +551,6 @@ class ExtensionsScanner extends Disposable { private readonly extensionsEnabledWithApiProposalVersion: string[]; constructor( - private readonly obsoleteFile: URI, @IExtensionsProfileScannerService protected readonly extensionsProfileScannerService: IExtensionsProfileScannerService, @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService, @IFileService protected readonly fileService: IFileService, @@ -571,15 +563,9 @@ class ExtensionsScanner extends Disposable { } async scanExtensions(input: ExtensionScannerInput): Promise { - const extensions = input.profile ? await this.scanExtensionsFromProfile(input) : await this.scanExtensionsFromLocation(input); - let obsolete: IStringDictionary = {}; - if (input.excludeObsolete && input.type === ExtensionType.User) { - try { - const raw = (await this.fileService.readFile(this.obsoleteFile)).value.toString(); - obsolete = JSON.parse(raw); - } catch (error) { /* ignore */ } - } - return isEmptyObject(obsolete) ? extensions : extensions.filter(e => !obsolete[ExtensionKey.create(e).toString()]); + return input.profile + ? this.scanExtensionsFromProfile(input) + : this.scanExtensionsFromLocation(input); } private async scanExtensionsFromLocation(input: ExtensionScannerInput): Promise { @@ -596,7 +582,7 @@ class ExtensionsScanner extends Disposable { if (input.type === ExtensionType.User && basename(c.resource).indexOf('.') === 0) { return null; } - const extensionScannerInput = new ExtensionScannerInput(c.resource, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.excludeObsolete, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations); + const extensionScannerInput = new ExtensionScannerInput(c.resource, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations); return this.scanExtension(extensionScannerInput); })); return coalesce(extensions) @@ -622,7 +608,7 @@ class ExtensionsScanner extends Disposable { const extensions = await Promise.all( scannedProfileExtensions.map(async extensionInfo => { if (filter(extensionInfo)) { - const extensionScannerInput = new ExtensionScannerInput(extensionInfo.location, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.excludeObsolete, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations); + const extensionScannerInput = new ExtensionScannerInput(extensionInfo.location, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations); return this.scanExtension(extensionScannerInput, extensionInfo.metadata); } return null; @@ -891,7 +877,6 @@ class CachedExtensionsScanner extends ExtensionsScanner { constructor( private readonly currentProfile: IUserDataProfile, - obsoleteFile: URI, @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @IExtensionsProfileScannerService extensionsProfileScannerService: IExtensionsProfileScannerService, @IUriIdentityService uriIdentityService: IUriIdentityService, @@ -900,7 +885,7 @@ class CachedExtensionsScanner extends ExtensionsScanner { @IEnvironmentService environmentService: IEnvironmentService, @ILogService logService: ILogService ) { - super(obsoleteFile, extensionsProfileScannerService, uriIdentityService, fileService, productService, environmentService, logService); + super(extensionsProfileScannerService, uriIdentityService, fileService, productService, environmentService, logService); } override async scanExtensions(input: ExtensionScannerInput): Promise { diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 92405eefb753c..762da10967f29 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -60,7 +60,7 @@ export interface INativeServerExtensionManagementService extends IExtensionManag readonly _serviceBrand: undefined; scanAllUserInstalledExtensions(): Promise; scanInstalledExtensionAtLocation(location: URI): Promise; - markAsUninstalled(...extensions: IExtension[]): Promise; + deleteExtensions(...extensions: IExtension[]): Promise; } type ExtractExtensionResult = { readonly local: ILocalExtension; readonly verificationStatus?: ExtensionSignatureVerificationCode }; @@ -222,8 +222,8 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return this.extensionsScanner.copyExtensions(fromProfileLocation, toProfileLocation, { version: this.productService.version, date: this.productService.date }); } - markAsUninstalled(...extensions: IExtension[]): Promise { - return this.extensionsScanner.setUninstalled(...extensions); + deleteExtensions(...extensions: IExtension[]): Promise { + return this.extensionsScanner.setExtensionsForRemoval(...extensions); } async cleanUp(): Promise { @@ -480,8 +480,20 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi continue; } - // Check if this is a directory - if (!(await this.fileService.stat(resource)).isDirectory) { + // Ignore changes to the deleted folder + if (this.uriIdentityService.extUri.basename(resource).endsWith(DELETED_FOLDER_POSTFIX)) { + continue; + } + + try { + // Check if this is a directory + if (!(await this.fileService.stat(resource)).isDirectory) { + continue; + } + } catch (error) { + if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { + this.logService.error(error); + } continue; } @@ -502,23 +514,10 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi private async addExtensionsToProfile(extensions: [ILocalExtension, Metadata | undefined][], profileLocation: URI): Promise { const localExtensions = extensions.map(e => e[0]); - await this.setInstalled(localExtensions); + await this.extensionsScanner.unsetExtensionsForRemoval(...localExtensions.map(extension => ExtensionKey.create(extension))); 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(); - 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); - await this.extensionsScanner.setInstalled(extensionKey); - this.logService.info('Removed the extension from uninstalled list:', extensionKey.id); - } - } } type UpdateMetadataErrorClassification = { @@ -536,8 +535,8 @@ type UpdateMetadataErrorEvent = { export class ExtensionsScanner extends Disposable { - private readonly uninstalledResource: URI; - private readonly uninstalledFileLimiter: Queue; + private readonly obsoletedResource: URI; + private readonly obsoleteFileLimiter: Queue; private readonly _onExtract = this._register(new Emitter()); readonly onExtract = this._onExtract.event; @@ -555,13 +554,13 @@ export class ExtensionsScanner extends Disposable { @ILogService private readonly logService: ILogService, ) { super(); - this.uninstalledResource = joinPath(this.extensionsScannerService.userExtensionsLocation, '.obsolete'); - this.uninstalledFileLimiter = new Queue(); + this.obsoletedResource = joinPath(this.extensionsScannerService.userExtensionsLocation, '.obsolete'); + this.obsoleteFileLimiter = new Queue(); } async cleanUp(): Promise { await this.removeTemporarilyDeletedFolders(); - await this.removeUninstalledExtensions(); + await this.deleteExtensionsMarkedForRemoval(); await this.initializeMetadata(); } @@ -720,42 +719,40 @@ export class ExtensionsScanner extends Disposable { return this.scanLocalExtension(local.location, local.type, profileLocation); } - async getUninstalledExtensions(): Promise> { - try { - return await this.withUninstalledExtensions(); - } catch (error) { - throw toExtensionManagementError(error, ExtensionManagementErrorCode.ReadUninstalled); - } - } - - async setUninstalled(...extensions: IExtension[]): Promise { + async setExtensionsForRemoval(...extensions: IExtension[]): Promise { const extensionKeys: ExtensionKey[] = extensions.map(e => ExtensionKey.create(e)); - await this.withUninstalledExtensions(uninstalled => + await this.withRemovedExtensions(removedExtensions => extensionKeys.forEach(extensionKey => { - uninstalled[extensionKey.toString()] = true; - this.logService.info('Marked extension as uninstalled', extensionKey.toString()); + removedExtensions[extensionKey.toString()] = true; + this.logService.info('Marked extension as removed', extensionKey.toString()); })); } - async setInstalled(extensionKey: ExtensionKey): Promise { + async unsetExtensionsForRemoval(...extensionKeys: ExtensionKey[]): Promise { try { - await this.withUninstalledExtensions(uninstalled => delete uninstalled[extensionKey.toString()]); + const results: boolean[] = []; + await this.withRemovedExtensions(removedExtensions => + extensionKeys.forEach(extensionKey => { + if (removedExtensions[extensionKey.toString()]) { + results.push(true); + delete removedExtensions[extensionKey.toString()]; + } else { + results.push(false); + } + })); + return results; } catch (error) { - throw toExtensionManagementError(error, ExtensionManagementErrorCode.UnsetUninstalled); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.UnsetRemoved); } } - async removeExtension(extension: ILocalExtension | IScannedExtension, type: string): Promise { + async deleteExtension(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); + await this.deleteExtensionFromLocation(extension.identifier.id, extension.location, type); + await this.unsetExtensionsForRemoval(ExtensionKey.create(extension)); } } - 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); @@ -792,11 +789,11 @@ export class ExtensionsScanner extends Disposable { this.logService.info(`Deleted ${type} extension from disk`, id, location.fsPath); } - private withUninstalledExtensions(updateFn?: (uninstalled: IStringDictionary) => void): Promise> { - return this.uninstalledFileLimiter.queue(async () => { + private withRemovedExtensions(updateFn?: (removed: IStringDictionary) => void): Promise> { + return this.obsoleteFileLimiter.queue(async () => { let raw: string | undefined; try { - const content = await this.fileService.readFile(this.uninstalledResource, 'utf8'); + const content = await this.fileService.readFile(this.obsoletedResource, 'utf8'); raw = content.value.toString(); } catch (error) { if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { @@ -804,23 +801,29 @@ export class ExtensionsScanner extends Disposable { } } - let uninstalled = {}; + let removed = {}; if (raw) { try { - uninstalled = JSON.parse(raw); + removed = JSON.parse(raw); } catch (e) { /* ignore */ } } if (updateFn) { - updateFn(uninstalled); - if (Object.keys(uninstalled).length) { - await this.fileService.writeFile(this.uninstalledResource, VSBuffer.fromString(JSON.stringify(uninstalled))); + updateFn(removed); + if (Object.keys(removed).length) { + await this.fileService.writeFile(this.obsoletedResource, VSBuffer.fromString(JSON.stringify(removed))); } else { - await this.fileService.del(this.uninstalledResource); + try { + await this.fileService.del(this.obsoletedResource); + } catch (error) { + if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { + throw error; + } + } } } - return uninstalled; + return removed; }); } @@ -898,19 +901,25 @@ export class ExtensionsScanner extends Disposable { })); } - private async removeUninstalledExtensions(): Promise { - const uninstalled = await this.getUninstalledExtensions(); - if (Object.keys(uninstalled).length === 0) { - this.logService.debug(`No uninstalled extensions found.`); + private async deleteExtensionsMarkedForRemoval(): Promise { + let removed: IStringDictionary; + try { + removed = await this.withRemovedExtensions(); + } catch (error) { + throw toExtensionManagementError(error, ExtensionManagementErrorCode.ReadRemoved); + } + + if (Object.keys(removed).length === 0) { + this.logService.debug(`No extensions are marked as removed.`); return; } - this.logService.debug(`Removing uninstalled extensions:`, Object.keys(uninstalled)); + this.logService.debug(`Deleting extensions marked as removed:`, Object.keys(removed)); - const extensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: true, includeUninstalled: true, includeInvalid: true }); // All user extensions + const extensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: true, includeInvalid: true }); // All user extensions const installed: Set = new Set(); for (const e of extensions) { - if (!uninstalled[ExtensionKey.create(e).toString()]) { + if (!removed[ExtensionKey.create(e).toString()]) { installed.add(e.identifier.id.toLowerCase()); } } @@ -928,8 +937,8 @@ export class ExtensionsScanner extends Disposable { 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 */ && removed[ExtensionKey.create(e).toString()]); + await Promise.allSettled(toRemove.map(e => this.deleteExtension(e, 'marked for removal'))); } private async removeTemporarilyDeletedFolders(): Promise { @@ -1021,7 +1030,7 @@ class InstallExtensionInProfileTask extends AbstractExtensionTask { - const uninstalled = await this.extensionsScanner.getUninstalledExtensions(); - if (!uninstalled[extensionKey.toString()]) { - return undefined; + private async unsetIfRemoved(extensionKey: ExtensionKey): Promise { + // If the same version of extension is marked as removed, remove it from there and return the local. + const [removed] = await this.extensionsScanner.unsetExtensionsForRemoval(extensionKey); + if (removed) { + this.logService.info('Removed the extension from removed list:', extensionKey.id); + const userExtensions = await this.extensionsScanner.scanAllUserExtensions(true); + return userExtensions.find(i => ExtensionKey.create(i).equals(extensionKey)); } - - 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)); + return undefined; } private async updateMetadata(extension: ILocalExtension, token: CancellationToken): Promise { @@ -1149,8 +1154,8 @@ class UninstallExtensionInProfileTask extends AbstractExtensionTask implem super(); } - protected async doRun(token: CancellationToken): Promise { - await this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension, this.options.profileLocation); + protected doRun(token: CancellationToken): Promise { + return this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension, this.options.profileLocation); } } diff --git a/src/vs/platform/extensionManagement/node/extensionsWatcher.ts b/src/vs/platform/extensionManagement/node/extensionsWatcher.ts index d2b65eaea553c..2c4e976a5a648 100644 --- a/src/vs/platform/extensionManagement/node/extensionsWatcher.ts +++ b/src/vs/platform/extensionManagement/node/extensionsWatcher.ts @@ -48,7 +48,7 @@ export class ExtensionsWatcher extends Disposable { await this.extensionsScannerService.initializeDefaultProfileExtensions(); await this.onDidChangeProfiles(this.userDataProfilesService.profiles); this.registerListeners(); - await this.uninstallExtensionsNotInProfiles(); + await this.deleteExtensionsNotInProfiles(); } private registerListeners(): void { @@ -102,7 +102,7 @@ export class ExtensionsWatcher extends Disposable { } private async onDidRemoveExtensions(e: DidRemoveProfileExtensionsEvent): Promise { - const extensionsToUninstall: IExtension[] = []; + const extensionsToDelete: IExtension[] = []; const promises: Promise[] = []; for (const extension of e.extensions) { const key = this.getKey(extension.identifier, extension.version); @@ -115,7 +115,7 @@ export class ExtensionsWatcher extends Disposable { promises.push(this.extensionManagementService.scanInstalledExtensionAtLocation(extension.location) .then(result => { if (result) { - extensionsToUninstall.push(result); + extensionsToDelete.push(result); } else { this.logService.info('Extension not found at the location', extension.location.toString()); } @@ -125,8 +125,8 @@ export class ExtensionsWatcher extends Disposable { } try { await Promise.all(promises); - if (extensionsToUninstall.length) { - await this.uninstallExtensionsNotInProfiles(extensionsToUninstall); + if (extensionsToDelete.length) { + await this.deleteExtensionsNotInProfiles(extensionsToDelete); } } catch (error) { this.logService.error(error); @@ -180,13 +180,13 @@ export class ExtensionsWatcher extends Disposable { } } - private async uninstallExtensionsNotInProfiles(toUninstall?: IExtension[]): Promise { - if (!toUninstall) { + private async deleteExtensionsNotInProfiles(toDelete?: IExtension[]): Promise { + if (!toDelete) { const installed = await this.extensionManagementService.scanAllUserInstalledExtensions(); - toUninstall = installed.filter(installedExtension => !this.allExtensions.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version))); + toDelete = installed.filter(installedExtension => !this.allExtensions.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version))); } - if (toUninstall.length) { - await this.extensionManagementService.markAsUninstalled(...toUninstall); + if (toDelete.length) { + await this.extensionManagementService.deleteExtensions(...toDelete); } } diff --git a/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts index 74d3ffcd738f8..551ba576d4459 100644 --- a/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts +++ b/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts @@ -224,31 +224,6 @@ suite('NativeExtensionsScanerService Test', () => { assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' }); }); - test('scan exclude uninstalled extensions', async () => { - await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' })); - await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub' })); - await instantiationService.get(IFileService).writeFile(joinPath(URI.file(instantiationService.get(INativeEnvironmentService).extensionsPath), '.obsolete'), VSBuffer.fromString(JSON.stringify({ 'pub.name2-1.0.0': true }))); - const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); - - const actual = await testObject.scanUserExtensions({}); - - assert.deepStrictEqual(actual.length, 1); - assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' }); - }); - - test('scan include uninstalled extensions', async () => { - await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' })); - await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub' })); - await instantiationService.get(IFileService).writeFile(joinPath(URI.file(instantiationService.get(INativeEnvironmentService).extensionsPath), '.obsolete'), VSBuffer.fromString(JSON.stringify({ 'pub.name2-1.0.0': true }))); - const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); - - const actual = await testObject.scanUserExtensions({ includeUninstalled: true }); - - assert.deepStrictEqual(actual.length, 2); - assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' }); - assert.deepStrictEqual(actual[1].identifier, { id: 'pub.name2' }); - }); - test('scan include invalid extensions', async () => { await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' })); await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub', engines: { vscode: '^1.67.0' } })); @@ -351,7 +326,7 @@ suite('ExtensionScannerInput', () => { ensureNoDisposablesAreLeakedInTestSuite(); test('compare inputs - location', () => { - const anInput = (location: URI, mtime: number | undefined) => new ExtensionScannerInput(location, mtime, undefined, undefined, false, undefined, ExtensionType.User, true, true, '1.1.1', undefined, undefined, true, undefined, {}); + const anInput = (location: URI, mtime: number | undefined) => new ExtensionScannerInput(location, mtime, undefined, undefined, false, undefined, ExtensionType.User, true, '1.1.1', undefined, undefined, true, undefined, {}); assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, undefined), anInput(ROOT, undefined)), true); assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, 100), anInput(ROOT, 100)), true); @@ -361,7 +336,7 @@ suite('ExtensionScannerInput', () => { }); test('compare inputs - application location', () => { - const anInput = (location: URI, mtime: number | undefined) => new ExtensionScannerInput(ROOT, undefined, location, mtime, false, undefined, ExtensionType.User, true, true, '1.1.1', undefined, undefined, true, undefined, {}); + const anInput = (location: URI, mtime: number | undefined) => new ExtensionScannerInput(ROOT, undefined, location, mtime, false, undefined, ExtensionType.User, true, '1.1.1', undefined, undefined, true, undefined, {}); assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, undefined), anInput(ROOT, undefined)), true); assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, 100), anInput(ROOT, 100)), true); @@ -371,7 +346,7 @@ suite('ExtensionScannerInput', () => { }); test('compare inputs - profile', () => { - const anInput = (profile: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined) => new ExtensionScannerInput(ROOT, undefined, undefined, undefined, profile, profileScanOptions, ExtensionType.User, true, true, '1.1.1', undefined, undefined, true, undefined, {}); + const anInput = (profile: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined) => new ExtensionScannerInput(ROOT, undefined, undefined, undefined, profile, profileScanOptions, ExtensionType.User, true, '1.1.1', undefined, undefined, true, undefined, {}); assert.strictEqual(ExtensionScannerInput.equals(anInput(true, { bailOutWhenFileNotFound: true }), anInput(true, { bailOutWhenFileNotFound: true })), true); assert.strictEqual(ExtensionScannerInput.equals(anInput(false, { bailOutWhenFileNotFound: true }), anInput(false, { bailOutWhenFileNotFound: true })), true); @@ -384,7 +359,7 @@ suite('ExtensionScannerInput', () => { }); test('compare inputs - extension type', () => { - const anInput = (type: ExtensionType) => new ExtensionScannerInput(ROOT, undefined, undefined, undefined, false, undefined, type, true, true, '1.1.1', undefined, undefined, true, undefined, {}); + const anInput = (type: ExtensionType) => new ExtensionScannerInput(ROOT, undefined, undefined, undefined, false, undefined, type, true, '1.1.1', undefined, undefined, true, undefined, {}); assert.strictEqual(ExtensionScannerInput.equals(anInput(ExtensionType.System), anInput(ExtensionType.System)), true); assert.strictEqual(ExtensionScannerInput.equals(anInput(ExtensionType.User), anInput(ExtensionType.User)), true); From 41be1c6b69760824c9fa42d4bcec5a406c41e9bb Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 18 Dec 2024 11:49:08 +0100 Subject: [PATCH 078/200] handle invalid withProgress invocation as external error (#236454) fixes https://github.com/Microsoft/vscode/issues/134892 --- src/vs/workbench/api/browser/mainThreadProgress.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/browser/mainThreadProgress.ts b/src/vs/workbench/api/browser/mainThreadProgress.ts index 6606b0d64bd5d..e6f4632b61bf0 100644 --- a/src/vs/workbench/api/browser/mainThreadProgress.ts +++ b/src/vs/workbench/api/browser/mainThreadProgress.ts @@ -9,6 +9,7 @@ import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions import { Action } from '../../../base/common/actions.js'; import { ICommandService } from '../../../platform/commands/common/commands.js'; import { localize } from '../../../nls.js'; +import { onUnexpectedExternalError } from '../../../base/common/errors.js'; class ManageExtensionAction extends Action { constructor(extensionId: string, label: string, commandService: ICommandService) { @@ -52,7 +53,13 @@ export class MainThreadProgress implements MainThreadProgressShape { options = notificationOptions; } - this._progressService.withProgress(options, task, () => this._proxy.$acceptProgressCanceled(handle)); + try { + this._progressService.withProgress(options, task, () => this._proxy.$acceptProgressCanceled(handle)); + } catch (err) { + // the withProgress-method will throw synchronously when invoked with bad options + // which is then an enternal/extension error + onUnexpectedExternalError(err); + } } $progressReport(handle: number, message: IProgressStep): void { From fca8bb2388430e67b42253199e87ed7fa325442a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2024 12:18:50 +0100 Subject: [PATCH 079/200] Deduplicate untitled workspaces with workspaces that restore (fix #234232) (#236457) --- .../electron-main/windowsMainService.ts | 58 ++++++++++++++----- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index f441f7d5fb76c..d403852efcc2a 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -57,6 +57,7 @@ import { ILoggerMainService } from '../../log/electron-main/loggerService.js'; import { IAuxiliaryWindowsMainService } from '../../auxiliaryWindow/electron-main/auxiliaryWindows.js'; import { IAuxiliaryWindow } from '../../auxiliaryWindow/electron-main/auxiliaryWindow.js'; import { ICSSDevelopmentService } from '../../cssDev/node/cssDevService.js'; +import { ResourceSet } from '../../../base/common/map.js'; //#region Helper Interfaces @@ -729,7 +730,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private async getPathsToOpen(openConfig: IOpenConfiguration): Promise { let pathsToOpen: IPathToOpen[]; let isCommandLineOrAPICall = false; - let restoredWindows = false; + let isRestoringPaths = false; // Extract paths: from API if (openConfig.urisToOpen && openConfig.urisToOpen.length > 0) { @@ -759,19 +760,26 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic pathsToOpen.push(Object.create(null)); // add an empty window if we did not have windows to restore } - restoredWindows = true; + isRestoringPaths = true; } - // Convert multiple folders into workspace (if opened via API or CLI) - // This will ensure to open these folders in one window instead of multiple - // If we are in `addMode`, we should not do this because in that case all - // folders should be added to the existing window. + // Handle the case of multiple folders being opened from CLI while we are + // not in `--add` mode by creating an untitled workspace, only if: + // - they all share the same remote authority + // - there is no existing workspace to open that matches these folders if (!openConfig.addMode && isCommandLineOrAPICall) { - const foldersToOpen = pathsToOpen.filter(path => isSingleFolderWorkspacePathToOpen(path)) as ISingleFolderWorkspacePathToOpen[]; + const foldersToOpen = pathsToOpen.filter(path => isSingleFolderWorkspacePathToOpen(path)); if (foldersToOpen.length > 1) { const remoteAuthority = foldersToOpen[0].remoteAuthority; - if (foldersToOpen.every(folderToOpen => isEqualAuthority(folderToOpen.remoteAuthority, remoteAuthority))) { // only if all folder have the same authority - const workspace = await this.workspacesManagementMainService.createUntitledWorkspace(foldersToOpen.map(folder => ({ uri: folder.workspace.uri }))); + if (foldersToOpen.every(folderToOpen => isEqualAuthority(folderToOpen.remoteAuthority, remoteAuthority))) { + let workspace: IWorkspaceIdentifier | undefined; + + const lastSessionWorkspaceMatchingFolders = await this.doGetWorkspaceMatchingFoldersFromLastSession(remoteAuthority, foldersToOpen); + if (lastSessionWorkspaceMatchingFolders) { + workspace = lastSessionWorkspaceMatchingFolders; + } else { + workspace = await this.workspacesManagementMainService.createUntitledWorkspace(foldersToOpen.map(folder => ({ uri: folder.workspace.uri }))); + } // Add workspace and remove folders thereby pathsToOpen.push({ workspace, remoteAuthority }); @@ -780,12 +788,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } } - // Check for `window.startup` setting to include all windows + // Check for `window.restoreWindows` setting to include all windows // from the previous session if this is the initial startup and we have // not restored windows already otherwise. - // Use `unshift` to ensure any new window to open comes last - // for proper focus treatment. - if (openConfig.initialStartup && !restoredWindows && this.configurationService.getValue('window')?.restoreWindows === 'preserve') { + // Use `unshift` to ensure any new window to open comes last for proper + // focus treatment. + if (openConfig.initialStartup && !isRestoringPaths && this.configurationService.getValue('window')?.restoreWindows === 'preserve') { const lastSessionPaths = await this.doGetPathsFromLastSession(); pathsToOpen.unshift(...lastSessionPaths.filter(path => isWorkspacePathToOpen(path) || isSingleFolderWorkspacePathToOpen(path) || path.backupPath)); } @@ -974,6 +982,30 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic return restoreWindows; } + private async doGetWorkspaceMatchingFoldersFromLastSession(remoteAuthority: string | undefined, folders: ISingleFolderWorkspacePathToOpen[]): Promise { + const workspaces = (await this.doGetPathsFromLastSession()).filter(path => isWorkspacePathToOpen(path)); + const folderUris = folders.map(folder => folder.workspace.uri); + + for (const { workspace } of workspaces) { + const resolvedWorkspace = await this.workspacesManagementMainService.resolveLocalWorkspace(workspace.configPath); + if ( + !resolvedWorkspace || + resolvedWorkspace.remoteAuthority !== remoteAuthority || + resolvedWorkspace.transient || + resolvedWorkspace.folders.length !== folders.length + ) { + continue; + } + + const folderSet = new ResourceSet(folderUris, uri => extUriBiasedIgnorePathCase.getComparisonKey(uri)); + if (resolvedWorkspace.folders.every(folder => folderSet.has(folder.uri))) { + return resolvedWorkspace; + } + } + + return undefined; + } + private async resolveOpenable(openable: IWindowOpenable, options: IPathResolveOptions = Object.create(null)): Promise { // handle file:// openables with some extra validation From 2f7beb56ff7319edc20b8f81c965da85738475e4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2024 12:46:34 +0100 Subject: [PATCH 080/200] Support for code page `1125` aka `cp866u` (fix #230438 (#236461) ) --- .../services/textfile/common/encoding.ts | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/services/textfile/common/encoding.ts b/src/vs/workbench/services/textfile/common/encoding.ts index b653dd7a84787..599f2309e6a59 100644 --- a/src/vs/workbench/services/textfile/common/encoding.ts +++ b/src/vs/workbench/services/textfile/common/encoding.ts @@ -615,151 +615,157 @@ export const SUPPORTED_ENCODINGS: EncodingsMap = { order: 20, guessableName: 'IBM866' }, + cp1125: { + labelLong: 'Cyrillic (CP 1125)', + labelShort: 'CP 1125', + order: 21, + guessableName: 'IBM1125' + }, iso88595: { labelLong: 'Cyrillic (ISO 8859-5)', labelShort: 'ISO 8859-5', - order: 21, + order: 22, guessableName: 'ISO-8859-5' }, koi8r: { labelLong: 'Cyrillic (KOI8-R)', labelShort: 'KOI8-R', - order: 22, + order: 23, guessableName: 'KOI8-R' }, koi8u: { labelLong: 'Cyrillic (KOI8-U)', labelShort: 'KOI8-U', - order: 23 + order: 24 }, iso885913: { labelLong: 'Estonian (ISO 8859-13)', labelShort: 'ISO 8859-13', - order: 24 + order: 25 }, windows1253: { labelLong: 'Greek (Windows 1253)', labelShort: 'Windows 1253', - order: 25, + order: 26, guessableName: 'windows-1253' }, iso88597: { labelLong: 'Greek (ISO 8859-7)', labelShort: 'ISO 8859-7', - order: 26, + order: 27, guessableName: 'ISO-8859-7' }, windows1255: { labelLong: 'Hebrew (Windows 1255)', labelShort: 'Windows 1255', - order: 27, + order: 28, guessableName: 'windows-1255' }, iso88598: { labelLong: 'Hebrew (ISO 8859-8)', labelShort: 'ISO 8859-8', - order: 28, + order: 29, guessableName: 'ISO-8859-8' }, iso885910: { labelLong: 'Nordic (ISO 8859-10)', labelShort: 'ISO 8859-10', - order: 29 + order: 30 }, iso885916: { labelLong: 'Romanian (ISO 8859-16)', labelShort: 'ISO 8859-16', - order: 30 + order: 31 }, windows1254: { labelLong: 'Turkish (Windows 1254)', labelShort: 'Windows 1254', - order: 31 + order: 32 }, iso88599: { labelLong: 'Turkish (ISO 8859-9)', labelShort: 'ISO 8859-9', - order: 32 + order: 33 }, windows1258: { labelLong: 'Vietnamese (Windows 1258)', labelShort: 'Windows 1258', - order: 33 + order: 34 }, gbk: { labelLong: 'Simplified Chinese (GBK)', labelShort: 'GBK', - order: 34 + order: 35 }, gb18030: { labelLong: 'Simplified Chinese (GB18030)', labelShort: 'GB18030', - order: 35 + order: 36 }, cp950: { labelLong: 'Traditional Chinese (Big5)', labelShort: 'Big5', - order: 36, + order: 37, guessableName: 'Big5' }, big5hkscs: { labelLong: 'Traditional Chinese (Big5-HKSCS)', labelShort: 'Big5-HKSCS', - order: 37 + order: 38 }, shiftjis: { labelLong: 'Japanese (Shift JIS)', labelShort: 'Shift JIS', - order: 38, + order: 39, guessableName: 'SHIFT_JIS' }, eucjp: { labelLong: 'Japanese (EUC-JP)', labelShort: 'EUC-JP', - order: 39, + order: 40, guessableName: 'EUC-JP' }, euckr: { labelLong: 'Korean (EUC-KR)', labelShort: 'EUC-KR', - order: 40, + order: 41, guessableName: 'EUC-KR' }, windows874: { labelLong: 'Thai (Windows 874)', labelShort: 'Windows 874', - order: 41 + order: 42 }, iso885911: { labelLong: 'Latin/Thai (ISO 8859-11)', labelShort: 'ISO 8859-11', - order: 42 + order: 43 }, koi8ru: { labelLong: 'Cyrillic (KOI8-RU)', labelShort: 'KOI8-RU', - order: 43 + order: 44 }, koi8t: { labelLong: 'Tajik (KOI8-T)', labelShort: 'KOI8-T', - order: 44 + order: 45 }, gb2312: { labelLong: 'Simplified Chinese (GB 2312)', labelShort: 'GB 2312', - order: 45, + order: 46, guessableName: 'GB2312' }, cp865: { labelLong: 'Nordic DOS (CP 865)', labelShort: 'CP 865', - order: 46 + order: 47 }, cp850: { labelLong: 'Western European DOS (CP 850)', labelShort: 'CP 850', - order: 47 + order: 48 } }; From 6c2faf1c5db3a438360b44a9f7b83bafac8efa0b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2024 13:06:31 +0100 Subject: [PATCH 081/200] chat - fix setup removal key (#236463) --- .../contrib/chat/browser/actions/chatActions.ts | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 92f6c42dd3df4..4cb15f40d68e7 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -531,13 +531,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandCenter, { submenu: MenuId.ChatCommandCenter, title: localize('title4', "Chat"), icon: Codicon.copilot, - when: ContextKeyExpr.and( - ContextKeyExpr.has('config.chat.commandCenter.enabled'), - ContextKeyExpr.or( - ChatContextKeys.Setup.installed, - ChatContextKeys.panelParticipantRegistered - ) - ), + when: ContextKeyExpr.has('config.chat.commandCenter.enabled'), order: 10001, }); @@ -547,13 +541,7 @@ registerAction2(class ToggleCopilotControl extends ToggleTitleBarConfigAction { 'chat.commandCenter.enabled', localize('toggle.chatControl', 'Copilot Controls'), localize('toggle.chatControlsDescription', "Toggle visibility of the Copilot Controls in title bar"), 4, false, - ContextKeyExpr.and( - ContextKeyExpr.has('config.window.commandCenter'), - ContextKeyExpr.or( - ChatContextKeys.Setup.installed, - ChatContextKeys.panelParticipantRegistered - ) - ) + ContextKeyExpr.has('config.window.commandCenter') ); } }); From 7579838b39047efb0f22236fea8e6d94ca6ea5e1 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Wed, 18 Dec 2024 14:12:34 +0100 Subject: [PATCH 082/200] Render the a background cover up only if really necessary (#236460) --- .../view/inlineEdits/sideBySideDiff.ts | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts index a64c52dcd90cc..5f36bec5b3367 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts @@ -14,6 +14,7 @@ import { ICommandService } from '../../../../../../platform/commands/common/comm import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { diffInserted, diffRemoved } from '../../../../../../platform/theme/common/colorRegistry.js'; import { darken, lighten, registerColor } from '../../../../../../platform/theme/common/colorUtils.js'; +import { IThemeService } from '../../../../../../platform/theme/common/themeService.js'; import { ICodeEditor } from '../../../../../browser/editorBrowser.js'; import { observableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; import { Point } from '../../../../../browser/point.js'; @@ -108,7 +109,8 @@ export class InlineEditsSideBySideDiff extends Disposable { originalDisplayRange: LineRange; } | undefined>, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @ICommandService private readonly _commandService: ICommandService + @ICommandService private readonly _commandService: ICommandService, + @IThemeService private readonly _themeService: IThemeService, ) { super(); @@ -487,19 +489,33 @@ export class InlineEditsSideBySideDiff extends Disposable { return extendedModifiedPathBuilder.build(); }); + private readonly _originalBackgroundColor = observableFromEvent(this, this._themeService.onDidColorThemeChange, () => { + return this._themeService.getColorTheme().getColor(originalBackgroundColor) ?? Color.transparent; + }); + private readonly _backgroundSvg = n.svg({ transform: 'translate(-0.5 -0.5)', style: { overflow: 'visible', pointerEvents: 'none', position: 'absolute' }, }, [ n.svgElem('path', { class: 'rightOfModifiedBackgroundCoverUp', - d: this._previewEditorLayoutInfo.map(layoutInfo => layoutInfo && new PathBuilder() - .moveTo(layoutInfo.code1) - .lineTo(layoutInfo.code1.deltaX(1000)) - .lineTo(layoutInfo.code2.deltaX(1000)) - .lineTo(layoutInfo.code2) - .build() - ), + d: derived(reader => { + const layoutInfo = this._previewEditorLayoutInfo.read(reader); + if (!layoutInfo) { + return undefined; + } + const originalBackgroundColor = this._originalBackgroundColor.read(reader); + if (originalBackgroundColor.isTransparent()) { + return undefined; + } + + return new PathBuilder() + .moveTo(layoutInfo.code1) + .lineTo(layoutInfo.code1.deltaX(1000)) + .lineTo(layoutInfo.code2.deltaX(1000)) + .lineTo(layoutInfo.code2) + .build(); + }), style: { fill: 'var(--vscode-editor-background, transparent)', } From 473620cdd2f4f38e3e4f409d96aeeb02d81db5dc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2024 14:14:46 +0100 Subject: [PATCH 083/200] Wrong use of disable store in searchResultsView.ts (fix #236456) (#236469) --- .../workbench/contrib/search/browser/searchResultsView.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts index 40df97b78cbbc..a4705ea7f15a0 100644 --- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts +++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -192,7 +192,7 @@ export class FolderMatchRenderer extends Disposable implements ICompressibleTree SearchContext.FileFocusKey.bindTo(contextKeyServiceMain).set(false); SearchContext.FolderFocusKey.bindTo(contextKeyServiceMain).set(true); - const instantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyServiceMain]))); + const instantiationService = disposables.add(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyServiceMain]))); const actions = disposables.add(instantiationService.createInstance(MenuWorkbenchToolBar, actionBarContainer, MenuId.SearchActionMenu, { menuOptions: { shouldForwardArgs: true @@ -292,7 +292,7 @@ export class FileMatchRenderer extends Disposable implements ICompressibleTreeRe SearchContext.FileFocusKey.bindTo(contextKeyServiceMain).set(true); SearchContext.FolderFocusKey.bindTo(contextKeyServiceMain).set(false); - const instantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyServiceMain]))); + const instantiationService = disposables.add(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyServiceMain]))); const actions = disposables.add(instantiationService.createInstance(MenuWorkbenchToolBar, actionBarContainer, MenuId.SearchActionMenu, { menuOptions: { shouldForwardArgs: true @@ -384,7 +384,7 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender SearchContext.FileFocusKey.bindTo(contextKeyServiceMain).set(false); SearchContext.FolderFocusKey.bindTo(contextKeyServiceMain).set(false); - const instantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyServiceMain]))); + const instantiationService = disposables.add(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyServiceMain]))); const actions = disposables.add(instantiationService.createInstance(MenuWorkbenchToolBar, actionBarContainer, MenuId.SearchActionMenu, { menuOptions: { shouldForwardArgs: true From b274aac2ca213a2ca9b9626b7060328f31cfffaf Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:46:43 +0100 Subject: [PATCH 084/200] Allow center layout with vertical groups (#236471) allow center layout with vertical groups --- src/vs/workbench/browser/layout.ts | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 84e2eaadb3c34..592bb4146af11 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -23,14 +23,14 @@ import { getMenuBarVisibility, IPath, hasNativeTitlebar, hasCustomTitlebar, Titl import { IHostService } from '../services/host/browser/host.js'; import { IBrowserWorkbenchEnvironmentService } from '../services/environment/browser/environmentService.js'; import { IEditorService } from '../services/editor/common/editorService.js'; -import { EditorGroupLayout, GroupsOrder, IEditorGroupsService } from '../services/editor/common/editorGroupsService.js'; +import { EditorGroupLayout, GroupOrientation, GroupsOrder, IEditorGroupsService } from '../services/editor/common/editorGroupsService.js'; import { SerializableGrid, ISerializableView, ISerializedGrid, Orientation, ISerializedNode, ISerializedLeafNode, Direction, IViewSize, Sizing } from '../../base/browser/ui/grid/grid.js'; import { Part } from './part.js'; import { IStatusbarService } from '../services/statusbar/browser/statusbar.js'; import { IFileService } from '../../platform/files/common/files.js'; import { isCodeEditor } from '../../editor/browser/editorBrowser.js'; import { coalesce } from '../../base/common/arrays.js'; -import { assertIsDefined } from '../../base/common/types.js'; +import { assertIsDefined, isDefined } from '../../base/common/types.js'; import { INotificationService, NotificationsFilter } from '../../platform/notification/common/notification.js'; import { IThemeService } from '../../platform/theme/common/themeService.js'; import { WINDOW_ACTIVE_BORDER, WINDOW_INACTIVE_BORDER } from '../common/theme.js'; @@ -1610,19 +1610,28 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi centerMainEditorLayout(active: boolean, skipLayout?: boolean): void { this.stateModel.setRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED, active); - const activeMainEditor = this.mainPartEditorService.activeEditor; + const mainVisibleEditors = this.editorGroupService.mainPart.groups.map(g => g.activeEditor).filter(isDefined); + const isEditorComplex = mainVisibleEditors.some(editor => { + if (editor instanceof DiffEditorInput) { + return this.configurationService.getValue('diffEditor.renderSideBySide'); + } else if (editor?.hasCapability(EditorInputCapabilities.MultipleEditors)) { + return true; + } + return false; + }); - let isEditorComplex = false; - if (activeMainEditor instanceof DiffEditorInput) { - isEditorComplex = this.configurationService.getValue('diffEditor.renderSideBySide'); - } else if (activeMainEditor?.hasCapability(EditorInputCapabilities.MultipleEditors)) { - isEditorComplex = true; + const layout = this.editorGroupService.getLayout(); + let hasMoreThanOneColumn = false; + if (layout.orientation === GroupOrientation.HORIZONTAL) { + hasMoreThanOneColumn = layout.groups.length > 1; + } else { + hasMoreThanOneColumn = layout.groups.some(g => g.groups && g.groups.length > 1); } const isCenteredLayoutAutoResizing = this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize'); if ( isCenteredLayoutAutoResizing && - ((this.editorGroupService.mainPart.groups.length > 1 && !this.editorGroupService.mainPart.hasMaximizedGroup()) || isEditorComplex) + ((hasMoreThanOneColumn && !this.editorGroupService.mainPart.hasMaximizedGroup()) || isEditorComplex) ) { active = false; // disable centered layout for complex editors or when there is more than one group } From 1efa0d219662095a77139bb0d18d664ee6230f3b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2024 14:59:40 +0100 Subject: [PATCH 085/200] refactor: enhance sync method and reuse for preview and some clean up (#236470) * refactor: enhance sync method and reuse for preview and some clean up * fix tests --- .../common/abstractSynchronizer.ts | 99 +++--- .../userDataSync/common/extensionsSync.ts | 3 +- .../userDataSync/common/globalStateSync.ts | 30 +- .../userDataSync/common/userDataSync.ts | 8 +- .../common/userDataSyncService.ts | 41 +-- .../test/common/keybindingsSync.test.ts | 6 +- .../test/common/settingsSync.test.ts | 2 +- .../test/common/snippetsSync.test.ts | 155 ++------- .../test/common/synchronizer.test.ts | 305 ++---------------- .../test/common/tasksSync.test.ts | 2 +- .../browser/editSessions.contribution.ts | 8 +- .../editSessions/common/workspaceStateSync.ts | 9 +- 12 files changed, 166 insertions(+), 502 deletions(-) diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index dc3b9950ad7d8..11c0315468aa1 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -26,7 +26,14 @@ import { getServiceMachineId } from '../../externalServices/common/serviceMachin import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js'; import { ITelemetryService } from '../../telemetry/common/telemetry.js'; import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js'; -import { Change, getLastSyncResourceUri, IRemoteUserData, IResourcePreview as IBaseResourcePreview, ISyncData, IUserDataSyncResourcePreview as IBaseSyncResourcePreview, IUserData, IUserDataSyncResourceInitializer, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, MergeState, PREVIEW_DIR_NAME, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, IUserDataResourceManifest, getPathSegments, IUserDataSyncResourceConflicts, IUserDataSyncResource } from './userDataSync.js'; +import { + Change, getLastSyncResourceUri, IRemoteUserData, IResourcePreview as IBaseResourcePreview, ISyncData, + IUserDataSyncResourcePreview as IBaseSyncResourcePreview, IUserData, IUserDataSyncResourceInitializer, IUserDataSyncLocalStoreService, + IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, + IUserDataSyncUtilService, MergeState, PREVIEW_DIR_NAME, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, + USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, IUserDataResourceManifest, getPathSegments, IUserDataSyncResourceConflicts, + IUserDataSyncResource, IUserDataSyncResourcePreview +} from './userDataSync.js'; import { IUserDataProfile, IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js'; type IncompatibleSyncSourceClassification = { @@ -114,6 +121,12 @@ interface ILastSyncUserDataState { [key: string]: any; } +export const enum SyncStrategy { + Preview = 'preview', // Merge the local and remote data without applying. + Merge = 'merge', // Merge the local and remote data and apply. + PullOrPush = 'pull-push', // Pull the remote data or push the local data. +} + export abstract class AbstractSynchroniser extends Disposable implements IUserDataSynchroniser { private syncPreviewPromise: CancelablePromise | null = null; @@ -180,7 +193,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa this.logService.info(`${this.syncResourceLogLabel}: In conflicts state and local change detected. Syncing again...`); const preview = await this.syncPreviewPromise!; this.syncPreviewPromise = null; - const status = await this.performSync(preview.remoteUserData, preview.lastSyncUserData, true, this.getUserDataSyncConfiguration()); + const status = await this.performSync(preview.remoteUserData, preview.lastSyncUserData, SyncStrategy.Merge, this.getUserDataSyncConfiguration()); this.setStatus(status); } @@ -202,28 +215,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa } } - async sync(manifest: IUserDataResourceManifest | null, headers: IHeaders = {}): Promise { - await this._sync(manifest, true, this.getUserDataSyncConfiguration(), headers); - } - - async preview(manifest: IUserDataResourceManifest | null, userDataSyncConfiguration: IUserDataSyncConfiguration, headers: IHeaders = {}): Promise { - return this._sync(manifest, false, userDataSyncConfiguration, headers); - } - - async apply(force: boolean, headers: IHeaders = {}): Promise { - try { - this.syncHeaders = { ...headers }; - - const status = await this.doApply(force); - this.setStatus(status); - - return this.syncPreviewPromise; - } finally { - this.syncHeaders = {}; - } - } - - private async _sync(manifest: IUserDataResourceManifest | null, apply: boolean, userDataSyncConfiguration: IUserDataSyncConfiguration, headers: IHeaders): Promise { + async sync(manifest: IUserDataResourceManifest | null, preview: boolean = false, userDataSyncConfiguration: IUserDataSyncConfiguration = this.getUserDataSyncConfiguration(), headers: IHeaders = {}): Promise { try { this.syncHeaders = { ...headers }; @@ -244,7 +236,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa try { const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getLatestRemoteUserData(manifest, lastSyncUserData); - status = await this.performSync(remoteUserData, lastSyncUserData, apply, userDataSyncConfiguration); + status = await this.performSync(remoteUserData, lastSyncUserData, preview ? SyncStrategy.Preview : SyncStrategy.Merge, userDataSyncConfiguration); if (status === SyncStatus.HasConflicts) { this.logService.info(`${this.syncResourceLogLabel}: Detected conflicts while synchronizing ${this.resource.toLowerCase()}.`); } else if (status === SyncStatus.Idle) { @@ -259,6 +251,19 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa } } + async apply(force: boolean, headers: IHeaders = {}): Promise { + try { + this.syncHeaders = { ...headers }; + + const status = await this.doApply(force); + this.setStatus(status); + + return this.syncPreviewPromise; + } finally { + this.syncHeaders = {}; + } + } + async replace(content: string): Promise { const syncData = this.parseSyncData(content); if (!syncData) { @@ -318,7 +323,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa return this.getRemoteUserData(lastSyncUserData); } - private async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, apply: boolean, userDataSyncConfiguration: IUserDataSyncConfiguration): Promise { + private async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, strategy: SyncStrategy, userDataSyncConfiguration: IUserDataSyncConfiguration): Promise { if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) { // current version is not compatible with cloud version this.telemetryService.publicLog2<{ source: string }, IncompatibleSyncSourceClassification>('sync/incompatible', { source: this.resource }); @@ -326,7 +331,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa } try { - return await this.doSync(remoteUserData, lastSyncUserData, apply, userDataSyncConfiguration); + return await this.doSync(remoteUserData, lastSyncUserData, strategy, userDataSyncConfiguration); } catch (e) { if (e instanceof UserDataSyncError) { switch (e.code) { @@ -334,7 +339,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa case UserDataSyncErrorCode.LocalPreconditionFailed: // Rejected as there is a new local version. Syncing again... this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize ${this.syncResourceLogLabel} as there is a new local version available. Synchronizing again...`); - return this.performSync(remoteUserData, lastSyncUserData, apply, userDataSyncConfiguration); + return this.performSync(remoteUserData, lastSyncUserData, strategy, userDataSyncConfiguration); case UserDataSyncErrorCode.Conflict: case UserDataSyncErrorCode.PreconditionFailed: @@ -348,19 +353,20 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa // and one of them successfully updated remote and last sync state. lastSyncUserData = await this.getLastSyncUserData(); - return this.performSync(remoteUserData, lastSyncUserData, apply, userDataSyncConfiguration); + return this.performSync(remoteUserData, lastSyncUserData, SyncStrategy.Merge, userDataSyncConfiguration); } } throw e; } } - protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, apply: boolean, userDataSyncConfiguration: IUserDataSyncConfiguration): Promise { + protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, strategy: SyncStrategy, userDataSyncConfiguration: IUserDataSyncConfiguration): Promise { try { const isRemoteDataFromCurrentMachine = await this.isRemoteDataFromCurrentMachine(remoteUserData); const acceptRemote = !isRemoteDataFromCurrentMachine && lastSyncUserData === null && this.getStoredLastSyncUserDataStateContent() !== undefined; - const merge = apply && !acceptRemote; + const merge = strategy === SyncStrategy.Preview || (strategy === SyncStrategy.Merge && !acceptRemote); + const apply = strategy === SyncStrategy.Merge || strategy === SyncStrategy.PullOrPush; // generate or use existing preview if (!this.syncPreviewPromise) { @@ -369,13 +375,26 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa let preview = await this.syncPreviewPromise; - if (apply && acceptRemote) { + if (strategy === SyncStrategy.Merge && acceptRemote) { this.logService.info(`${this.syncResourceLogLabel}: Accepting remote because it was synced before and the last sync data is not available.`); for (const resourcePreview of preview.resourcePreviews) { preview = (await this.accept(resourcePreview.remoteResource)) || preview; } } + else if (strategy === SyncStrategy.PullOrPush) { + for (const resourcePreview of preview.resourcePreviews) { + if (resourcePreview.mergeState === MergeState.Accepted) { + continue; + } + if (remoteUserData.ref === lastSyncUserData?.ref || isRemoteDataFromCurrentMachine) { + preview = (await this.accept(resourcePreview.localResource)) ?? preview; + } else { + preview = (await this.accept(resourcePreview.remoteResource)) ?? preview; + } + } + } + this.updateConflicts(preview.resourcePreviews); if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) { return SyncStatus.HasConflicts; @@ -396,22 +415,6 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa } } - async merge(resource: URI): Promise { - await this.updateSyncResourcePreview(resource, async (resourcePreview) => { - const mergeResult = await this.getMergeResult(resourcePreview, CancellationToken.None); - await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(mergeResult?.content || '')); - const acceptResult: IAcceptResult | undefined = mergeResult && !mergeResult.hasConflicts - ? await this.getAcceptResult(resourcePreview, resourcePreview.previewResource, undefined, CancellationToken.None) - : undefined; - resourcePreview.acceptResult = acceptResult; - resourcePreview.mergeState = mergeResult.hasConflicts ? MergeState.Conflict : acceptResult ? MergeState.Accepted : MergeState.Preview; - resourcePreview.localChange = acceptResult ? acceptResult.localChange : mergeResult.localChange; - resourcePreview.remoteChange = acceptResult ? acceptResult.remoteChange : mergeResult.remoteChange; - return resourcePreview; - }); - return this.syncPreviewPromise; - } - async accept(resource: URI, content?: string | null): Promise { await this.updateSyncResourcePreview(resource, async (resourcePreview) => { const acceptResult = await this.getAcceptResult(resourcePreview, resource, content, CancellationToken.None); diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 4054babf6f051..2269f4e3c58dd 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -242,7 +242,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse private async acceptLocal(resourcePreview: IExtensionResourcePreview): Promise { const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.syncResource.profile.extensionsResource); const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions); - const mergeResult = merge(resourcePreview.localExtensions, null, null, resourcePreview.skippedExtensions, ignoredExtensions, resourcePreview.builtinExtensions); + const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null; + const mergeResult = merge(resourcePreview.localExtensions, remoteExtensions, remoteExtensions, resourcePreview.skippedExtensions, ignoredExtensions, resourcePreview.builtinExtensions); const { local, remote } = mergeResult; return { content: resourcePreview.localContent, diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 75ea244643888..e37893825af17 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -194,25 +194,37 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } private async acceptLocal(resourcePreview: IGlobalStateResourcePreview): Promise { - return { - content: resourcePreview.localContent, - local: { added: {}, removed: [], updated: {} }, - remote: { added: Object.keys(resourcePreview.localUserData.storage), removed: [], updated: [], all: resourcePreview.localUserData.storage }, - localChange: Change.None, - remoteChange: Change.Modified, - }; + if (resourcePreview.remoteContent !== null) { + const remoteGlobalState: IGlobalState = JSON.parse(resourcePreview.remoteContent); + const { local, remote } = merge(resourcePreview.localUserData.storage, remoteGlobalState.storage, remoteGlobalState.storage, resourcePreview.storageKeys, this.logService); + return { + content: resourcePreview.remoteContent, + local, + remote, + localChange: Change.None, + remoteChange: remote.all !== null ? Change.Modified : Change.None, + }; + } else { + return { + content: resourcePreview.localContent, + local: { added: {}, removed: [], updated: {} }, + remote: { added: Object.keys(resourcePreview.localUserData.storage), removed: [], updated: [], all: resourcePreview.localUserData.storage }, + localChange: Change.None, + remoteChange: Change.Modified, + }; + } } private async acceptRemote(resourcePreview: IGlobalStateResourcePreview): Promise { if (resourcePreview.remoteContent !== null) { const remoteGlobalState: IGlobalState = JSON.parse(resourcePreview.remoteContent); - const { local, remote } = merge(resourcePreview.localUserData.storage, remoteGlobalState.storage, null, resourcePreview.storageKeys, this.logService); + const { local, remote } = merge(resourcePreview.localUserData.storage, remoteGlobalState.storage, resourcePreview.localUserData.storage, resourcePreview.storageKeys, this.logService); return { content: resourcePreview.remoteContent, local, remote, localChange: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0 ? Change.Modified : Change.None, - remoteChange: remote !== null ? Change.Modified : Change.None, + remoteChange: Change.None, }; } else { return { diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index c3224e18bddb0..02824fa34676a 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -498,14 +498,10 @@ export interface IUserDataSynchroniser { readonly onDidChangeLocal: Event; - sync(manifest: IUserDataResourceManifest | null, headers: IHeaders): Promise; - stop(): Promise; - - preview(manifest: IUserDataResourceManifest | null, userDataSyncConfiguration: IUserDataSyncConfiguration, headers: IHeaders): Promise; + sync(manifest: IUserDataResourceManifest | null, preview: boolean, userDataSyncConfiguration: IUserDataSyncConfiguration, headers: IHeaders): Promise; accept(resource: URI, content?: string | null): Promise; - merge(resource: URI): Promise; - discard(resource: URI): Promise; apply(force: boolean, headers: IHeaders): Promise; + stop(): Promise; hasPreviouslySynced(): Promise; hasLocalData(): Promise; diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index a18be8e399697..46a443581b99f 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -28,10 +28,10 @@ import { SnippetsSynchroniser } from './snippetsSync.js'; import { TasksSynchroniser } from './tasksSync.js'; import { UserDataProfilesManifestSynchroniser } from './userDataProfilesManifestSync.js'; import { - ALL_SYNC_RESOURCES, Change, createSyncHeaders, IUserDataManualSyncTask, IUserDataSyncResourceConflicts, IUserDataSyncResourceError, + ALL_SYNC_RESOURCES, createSyncHeaders, IUserDataManualSyncTask, IUserDataSyncResourceConflicts, IUserDataSyncResourceError, IUserDataSyncResource, ISyncResourceHandle, IUserDataSyncTask, ISyncUserDataProfile, IUserDataManifest, IUserDataResourceManifest, IUserDataSyncConfiguration, IUserDataSyncEnablementService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, - MergeState, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreError, USER_DATA_SYNC_CONFIGURATION_SCOPE, IUserDataSyncResourceProviderService, IUserDataActivityData, IUserDataSyncLocalStoreService + SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreError, USER_DATA_SYNC_CONFIGURATION_SCOPE, IUserDataSyncResourceProviderService, IUserDataActivityData, IUserDataSyncLocalStoreService, } from './userDataSync.js'; type SyncErrorClassification = { @@ -200,7 +200,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ }; } - private async sync(manifest: IUserDataManifest | null, merge: boolean, executionId: string, token: CancellationToken): Promise { + private async sync(manifest: IUserDataManifest | null, preview: boolean, executionId: string, token: CancellationToken): Promise { this._syncErrors = []; try { if (this.status !== SyncStatus.HasConflicts) { @@ -209,7 +209,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ // Sync Default Profile First const defaultProfileSynchronizer = this.getOrCreateActiveProfileSynchronizer(this.userDataProfilesService.defaultProfile, undefined); - this._syncErrors.push(...await this.syncProfile(defaultProfileSynchronizer, manifest, merge, executionId, token)); + this._syncErrors.push(...await this.syncProfile(defaultProfileSynchronizer, manifest, preview, executionId, token)); // Sync other profiles const userDataProfileManifestSynchronizer = defaultProfileSynchronizer.enabled.find(s => s.resource === SyncResource.Profiles); @@ -218,7 +218,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (token.isCancellationRequested) { return; } - await this.syncRemoteProfiles(syncProfiles, manifest, merge, executionId, token); + await this.syncRemoteProfiles(syncProfiles, manifest, preview, executionId, token); } } finally { if (this.status !== SyncStatus.HasConflicts) { @@ -228,7 +228,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } - private async syncRemoteProfiles(remoteProfiles: ISyncUserDataProfile[], manifest: IUserDataManifest | null, merge: boolean, executionId: string, token: CancellationToken): Promise { + private async syncRemoteProfiles(remoteProfiles: ISyncUserDataProfile[], manifest: IUserDataManifest | null, preview: boolean, executionId: string, token: CancellationToken): Promise { for (const syncProfile of remoteProfiles) { if (token.isCancellationRequested) { return; @@ -240,7 +240,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } this.logService.info('Syncing profile.', syncProfile.name); const profileSynchronizer = this.getOrCreateActiveProfileSynchronizer(profile, syncProfile); - this._syncErrors.push(...await this.syncProfile(profileSynchronizer, manifest, merge, executionId, token)); + this._syncErrors.push(...await this.syncProfile(profileSynchronizer, manifest, preview, executionId, token)); } // Dispose & Delete profile synchronizers which do not exist anymore for (const [key, profileSynchronizerItem] of this.activeProfileSynchronizers.entries()) { @@ -285,8 +285,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } - private async syncProfile(profileSynchronizer: ProfileSynchronizer, manifest: IUserDataManifest | null, merge: boolean, executionId: string, token: CancellationToken): Promise { - const errors = await profileSynchronizer.sync(manifest, merge, executionId, token); + private async syncProfile(profileSynchronizer: ProfileSynchronizer, manifest: IUserDataManifest | null, preview: boolean, executionId: string, token: CancellationToken): Promise { + const errors = await profileSynchronizer.sync(manifest, preview, executionId, token); return errors.map(([syncResource, error]) => ({ profile: profileSynchronizer.profile, syncResource, error })); } @@ -715,7 +715,7 @@ class ProfileSynchronizer extends Disposable { } } - async sync(manifest: IUserDataManifest | null, merge: boolean, executionId: string, token: CancellationToken): Promise<[SyncResource, UserDataSyncError][]> { + async sync(manifest: IUserDataManifest | null, preview: boolean, executionId: string, token: CancellationToken): Promise<[SyncResource, UserDataSyncError][]> { // Return if cancellation is requested if (token.isCancellationRequested) { @@ -731,7 +731,7 @@ class ProfileSynchronizer extends Disposable { const syncErrors: [SyncResource, UserDataSyncError][] = []; const syncHeaders = createSyncHeaders(executionId); const resourceManifest: IUserDataResourceManifest | null = (this.collection ? manifest?.collections?.[this.collection]?.latest : manifest?.latest) ?? null; - const userDataSyncConfiguration = merge ? await this.getUserDataSyncConfiguration(resourceManifest) : {}; + const userDataSyncConfiguration = preview ? await this.getUserDataSyncConfiguration(resourceManifest) : this.getLocalUserDataSyncConfiguration(); for (const synchroniser of synchronizers) { // Return if cancellation is requested if (token.isCancellationRequested) { @@ -744,18 +744,7 @@ class ProfileSynchronizer extends Disposable { } try { - if (merge) { - const preview = await synchroniser.preview(resourceManifest, userDataSyncConfiguration, syncHeaders); - if (preview) { - for (const resourcePreview of preview.resourcePreviews) { - if ((resourcePreview.localChange !== Change.None || resourcePreview.remoteChange !== Change.None) && resourcePreview.mergeState === MergeState.Preview) { - await synchroniser.merge(resourcePreview.previewResource); - } - } - } - } else { - await synchroniser.sync(resourceManifest, syncHeaders); - } + await synchroniser.sync(resourceManifest, preview, userDataSyncConfiguration, syncHeaders); } catch (e) { const userDataSyncError = UserDataSyncError.toUserDataSyncError(e); reportUserDataSyncError(userDataSyncError, executionId, this.userDataSyncStoreManagementService, this.telemetryService); @@ -825,7 +814,7 @@ class ProfileSynchronizer extends Disposable { if (!this.profile.isDefault) { return {}; } - const local = this.configurationService.getValue(USER_DATA_SYNC_CONFIGURATION_SCOPE); + const local = this.getLocalUserDataSyncConfiguration(); const settingsSynchronizer = this.enabled.find(synchronizer => synchronizer instanceof SettingsSynchroniser); if (settingsSynchronizer) { const remote = await (settingsSynchronizer).getRemoteUserDataSyncConfiguration(manifest); @@ -834,6 +823,10 @@ class ProfileSynchronizer extends Disposable { return local; } + private getLocalUserDataSyncConfiguration(): IUserDataSyncConfiguration { + return this.configurationService.getValue(USER_DATA_SYNC_CONFIGURATION_SCOPE); + } + private setStatus(status: SyncStatus): void { if (this._status !== status) { this._status = status; diff --git a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts index 00841916bbe3e..c0103898ddcd2 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts @@ -196,11 +196,11 @@ suite('KeybindingsSync', () => { await fileService.del(keybindingsResource); } - const preview = (await testObject.preview(await client.getResourceManifest(), {}))!; + const preview = await testObject.sync(await client.getResourceManifest(), true); server.reset(); - const content = await testObject.resolveContent(preview.resourcePreviews[0].remoteResource); - await testObject.accept(preview.resourcePreviews[0].remoteResource, content); + const content = await testObject.resolveContent(preview!.resourcePreviews[0].remoteResource); + await testObject.accept(preview!.resourcePreviews[0].remoteResource, content); await testObject.apply(false); assert.deepStrictEqual(server.requests, []); }); diff --git a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts index 914ae83a5d473..b3c6eee9926d4 100644 --- a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts @@ -569,7 +569,7 @@ suite('SettingsSync - Manual', () => { }`; await updateSettings(settingsContent, client); - let preview = await testObject.preview(await client.getResourceManifest(), {}); + let preview = await testObject.sync(await client.getResourceManifest(), true); assert.strictEqual(testObject.status, SyncStatus.Syncing); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); preview = await testObject.apply(false); diff --git a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts index 89e9c59c48be4..0e37d04d493c7 100644 --- a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts @@ -674,49 +674,12 @@ suite('SnippetsSync', () => { assert.ok(!await fileService.exists(dirname(conflicts[0].previewResource))); }); - test('merge when there are multiple snippets and only one snippet is merged', async () => { - const environmentService = testClient.instantiationService.get(IEnvironmentService); - - await updateSnippet('html.json', htmlSnippet2, testClient); - await updateSnippet('typescript.json', tsSnippet2, testClient); - let preview = await testObject.preview(await testClient.getResourceManifest(), {}); - - assert.strictEqual(testObject.status, SyncStatus.Syncing); - assertPreviews(preview!.resourcePreviews, - [ - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), - ]); - assert.deepStrictEqual(testObject.conflicts.conflicts, []); - - preview = await testObject.merge(preview!.resourcePreviews[0].localResource); - - assert.strictEqual(testObject.status, SyncStatus.Syncing); - assertPreviews(preview!.resourcePreviews, - [ - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), - ]); - assert.deepStrictEqual(testObject.conflicts.conflicts, []); - }); - test('merge when there are multiple snippets and all snippets are merged', async () => { const environmentService = testClient.instantiationService.get(IEnvironmentService); await updateSnippet('html.json', htmlSnippet2, testClient); await updateSnippet('typescript.json', tsSnippet2, testClient); - let preview = await testObject.preview(await testClient.getResourceManifest(), {}); - - assert.strictEqual(testObject.status, SyncStatus.Syncing); - assertPreviews(preview!.resourcePreviews, - [ - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), - ]); - assert.deepStrictEqual(testObject.conflicts.conflicts, []); - - preview = await testObject.merge(preview!.resourcePreviews[0].localResource); - preview = await testObject.merge(preview!.resourcePreviews[1].localResource); + const preview = await testObject.sync(await testClient.getResourceManifest(), true); assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, @@ -728,22 +691,9 @@ suite('SnippetsSync', () => { }); test('merge when there are multiple snippets and all snippets are merged and applied', async () => { - const environmentService = testClient.instantiationService.get(IEnvironmentService); - await updateSnippet('html.json', htmlSnippet2, testClient); await updateSnippet('typescript.json', tsSnippet2, testClient); - let preview = await testObject.preview(await testClient.getResourceManifest(), {}); - - assert.strictEqual(testObject.status, SyncStatus.Syncing); - assertPreviews(preview!.resourcePreviews, - [ - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), - ]); - assert.deepStrictEqual(testObject.conflicts.conflicts, []); - - preview = await testObject.merge(preview!.resourcePreviews[0].localResource); - preview = await testObject.merge(preview!.resourcePreviews[1].localResource); + let preview = await testObject.sync(await testClient.getResourceManifest(), true); preview = await testObject.apply(false); assert.strictEqual(testObject.status, SyncStatus.Idle); @@ -759,17 +709,7 @@ suite('SnippetsSync', () => { await updateSnippet('html.json', htmlSnippet1, testClient); await updateSnippet('typescript.json', tsSnippet2, testClient); - let preview = await testObject.preview(await testClient.getResourceManifest(), {}); - - assert.strictEqual(testObject.status, SyncStatus.Syncing); - assertPreviews(preview!.resourcePreviews, - [ - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), - ]); - assert.deepStrictEqual(testObject.conflicts.conflicts, []); - - preview = await testObject.merge(preview!.resourcePreviews[0].localResource); + const preview = await testObject.sync(await testClient.getResourceManifest(), true); assert.strictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, @@ -780,25 +720,14 @@ suite('SnippetsSync', () => { assert.deepStrictEqual(testObject.conflicts.conflicts, []); }); - test('merge when there are multiple snippets and one snippet has no changes and one snippet is merged and applied', async () => { - const environmentService = testClient.instantiationService.get(IEnvironmentService); - + test('merge when there are multiple snippets and one snippet has no changes and snippets is merged and applied', async () => { await updateSnippet('html.json', htmlSnippet1, client2); await client2.sync(); await updateSnippet('html.json', htmlSnippet1, testClient); await updateSnippet('typescript.json', tsSnippet2, testClient); - let preview = await testObject.preview(await testClient.getResourceManifest(), {}); - - assert.strictEqual(testObject.status, SyncStatus.Syncing); - assertPreviews(preview!.resourcePreviews, - [ - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), - ]); - assert.deepStrictEqual(testObject.conflicts.conflicts, []); + let preview = await testObject.sync(await testClient.getResourceManifest(), true); - preview = await testObject.merge(preview!.resourcePreviews[0].localResource); preview = await testObject.apply(false); assert.strictEqual(testObject.status, SyncStatus.Idle); @@ -806,7 +735,7 @@ suite('SnippetsSync', () => { assert.deepStrictEqual(testObject.conflicts.conflicts, []); }); - test('merge when there are multiple snippets with conflicts and only one snippet is merged', async () => { + test('merge when there are multiple snippets with conflicts and all snippets are merged', async () => { const environmentService = testClient.instantiationService.get(IEnvironmentService); await updateSnippet('html.json', htmlSnippet1, client2); @@ -815,17 +744,7 @@ suite('SnippetsSync', () => { await updateSnippet('html.json', htmlSnippet2, testClient); await updateSnippet('typescript.json', tsSnippet2, testClient); - let preview = await testObject.preview(await testClient.getResourceManifest(), {}); - - assert.strictEqual(testObject.status, SyncStatus.Syncing); - assertPreviews(preview!.resourcePreviews, - [ - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), - ]); - assert.deepStrictEqual(testObject.conflicts.conflicts, []); - - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + const preview = await testObject.sync(await testClient.getResourceManifest(), true); assert.strictEqual(testObject.status, SyncStatus.HasConflicts); assertPreviews(preview!.resourcePreviews, @@ -836,10 +755,11 @@ suite('SnippetsSync', () => { assertPreviews(testObject.conflicts.conflicts, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); }); - test('merge when there are multiple snippets with conflicts and all snippets are merged', async () => { + test('accept when there are multiple snippets with conflicts and only one snippet is accepted', async () => { const environmentService = testClient.instantiationService.get(IEnvironmentService); await updateSnippet('html.json', htmlSnippet1, client2); @@ -848,18 +768,7 @@ suite('SnippetsSync', () => { await updateSnippet('html.json', htmlSnippet2, testClient); await updateSnippet('typescript.json', tsSnippet2, testClient); - let preview = await testObject.preview(await testClient.getResourceManifest(), {}); - - assert.strictEqual(testObject.status, SyncStatus.Syncing); - assertPreviews(preview!.resourcePreviews, - [ - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), - ]); - assert.deepStrictEqual(testObject.conflicts.conflicts, []); - - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); - preview = await testObject.merge(preview!.resourcePreviews[1].previewResource); + let preview = await testObject.sync(await testClient.getResourceManifest(), true); assert.strictEqual(testObject.status, SyncStatus.HasConflicts); assertPreviews(preview!.resourcePreviews, @@ -872,36 +781,19 @@ suite('SnippetsSync', () => { joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - }); - test('accept when there are multiple snippets with conflicts and only one snippet is accepted', async () => { - const environmentService = testClient.instantiationService.get(IEnvironmentService); - - await updateSnippet('html.json', htmlSnippet1, client2); - await updateSnippet('typescript.json', tsSnippet1, client2); - await client2.sync(); - - await updateSnippet('html.json', htmlSnippet2, testClient); - await updateSnippet('typescript.json', tsSnippet2, testClient); - let preview = await testObject.preview(await testClient.getResourceManifest(), {}); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2); - assert.strictEqual(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.HasConflicts); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepStrictEqual(testObject.conflicts.conflicts, []); - - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2); - - assert.strictEqual(testObject.status, SyncStatus.Syncing); - assertPreviews(preview!.resourcePreviews, + assertPreviews(testObject.conflicts.conflicts, [ - joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepStrictEqual(testObject.conflicts.conflicts, []); }); test('accept when there are multiple snippets with conflicts and all snippets are accepted', async () => { @@ -913,15 +805,19 @@ suite('SnippetsSync', () => { await updateSnippet('html.json', htmlSnippet2, testClient); await updateSnippet('typescript.json', tsSnippet2, testClient); - let preview = await testObject.preview(await testClient.getResourceManifest(), {}); + let preview = await testObject.sync(await testClient.getResourceManifest(), true); - assert.strictEqual(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.HasConflicts); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepStrictEqual(testObject.conflicts.conflicts, []); + assertPreviews(testObject.conflicts.conflicts, + [ + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), + ]); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2); preview = await testObject.accept(preview!.resourcePreviews[1].previewResource, tsSnippet2); @@ -937,22 +833,25 @@ suite('SnippetsSync', () => { test('accept when there are multiple snippets with conflicts and all snippets are accepted and applied', async () => { const environmentService = testClient.instantiationService.get(IEnvironmentService); - await updateSnippet('html.json', htmlSnippet1, client2); await updateSnippet('typescript.json', tsSnippet1, client2); await client2.sync(); await updateSnippet('html.json', htmlSnippet2, testClient); await updateSnippet('typescript.json', tsSnippet2, testClient); - let preview = await testObject.preview(await testClient.getResourceManifest(), {}); + let preview = await testObject.sync(await testClient.getResourceManifest(), true); - assert.strictEqual(testObject.status, SyncStatus.Syncing); + assert.strictEqual(testObject.status, SyncStatus.HasConflicts); assertPreviews(preview!.resourcePreviews, [ joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), ]); - assert.deepStrictEqual(testObject.conflicts.conflicts, []); + assertPreviews(testObject.conflicts.conflicts, + [ + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'), + joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'), + ]); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2); preview = await testObject.accept(preview!.resourcePreviews[1].previewResource, tsSnippet2); diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index 4dfec2707115a..31a5580366054 100644 --- a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -15,7 +15,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/c import { IFileService } from '../../../files/common/files.js'; import { IStorageService, StorageScope } from '../../../storage/common/storage.js'; import { IUserDataProfilesService } from '../../../userDataProfile/common/userDataProfile.js'; -import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from '../../common/abstractSynchronizer.js'; +import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview, SyncStrategy } from '../../common/abstractSynchronizer.js'; import { Change, IRemoteUserData, IResourcePreview as IBaseResourcePreview, IUserDataResourceManifest, IUserDataSyncConfiguration, IUserDataSyncStoreService, MergeState, SyncResource, SyncStatus, USER_DATA_SYNC_SCHEME } from '../../common/userDataSync.js'; import { UserDataSyncClient, UserDataSyncTestServer } from './userDataSyncClient.js'; @@ -45,7 +45,7 @@ class TestSynchroniser extends AbstractSynchroniser { return super.getLatestRemoteUserData(manifest, lastSyncUserData); } - protected override async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, apply: boolean, userDataSyncConfiguration: IUserDataSyncConfiguration): Promise { + protected override async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, strategy: SyncStrategy, userDataSyncConfiguration: IUserDataSyncConfiguration): Promise { this.cancelled = false; this.onDoSyncCall.fire(); await this.syncBarrier.wait(); @@ -54,7 +54,7 @@ class TestSynchroniser extends AbstractSynchroniser { return SyncStatus.Idle; } - return super.doSync(remoteUserData, lastSyncUserData, apply, userDataSyncConfiguration); + return super.doSync(remoteUserData, lastSyncUserData, strategy, userDataSyncConfiguration); } protected override async generateSyncPreview(remoteUserData: IRemoteUserData): Promise { @@ -536,22 +536,7 @@ suite('TestSynchronizer - Manual Sync', () => { testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); - const preview = await testObject.preview(await client.getResourceManifest(), {}); - - assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); - assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assertConflicts(testObject.conflicts.conflicts, []); - }); - }); - - test('preview -> merge', async () => { - await runWithFakedTimers({}, async () => { - const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); - testObject.syncResult = { hasConflicts: false, hasError: false }; - testObject.syncBarrier.open(); - - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + const preview = await testObject.sync(await client.getResourceManifest(), true); assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); @@ -566,24 +551,7 @@ suite('TestSynchronizer - Manual Sync', () => { testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); - - assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); - assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); - assertConflicts(testObject.conflicts.conflicts, []); - }); - }); - - test('preview -> merge -> accept', async () => { - await runWithFakedTimers({}, async () => { - const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); - testObject.syncResult = { hasConflicts: false, hasError: false }; - testObject.syncBarrier.open(); - - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + let preview = await testObject.sync(await client.getResourceManifest(), true); preview = await testObject.accept(preview!.resourcePreviews[0].localResource); assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); @@ -601,8 +569,7 @@ suite('TestSynchronizer - Manual Sync', () => { await testObject.sync(await client.getResourceManifest()); const manifest = await client.getResourceManifest(); - let preview = await testObject.preview(manifest, {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + let preview = await testObject.sync(manifest, true); preview = await testObject.apply(false); assert.deepStrictEqual(testObject.status, SyncStatus.Idle); @@ -624,7 +591,7 @@ suite('TestSynchronizer - Manual Sync', () => { const manifest = await client.getResourceManifest(); const expectedContent = manifest![testObject.resource]; - let preview = await testObject.preview(manifest, {}); + let preview = await testObject.sync(manifest, true); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); preview = await testObject.apply(false); @@ -637,36 +604,13 @@ suite('TestSynchronizer - Manual Sync', () => { }); }); - test('preview -> merge -> accept -> apply', async () => { + test('preivew -> discard', async () => { await runWithFakedTimers({}, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); - await testObject.sync(await client.getResourceManifest()); - const expectedContent = (await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(); - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); - preview = await testObject.accept(preview!.resourcePreviews[0].localResource); - preview = await testObject.apply(false); - - assert.deepStrictEqual(testObject.status, SyncStatus.Idle); - assert.strictEqual(preview, null); - assertConflicts(testObject.conflicts.conflicts, []); - - assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); - assert.strictEqual((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); - }); - }); - - test('preivew -> merge -> discard', async () => { - await runWithFakedTimers({}, async () => { - const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); - testObject.syncResult = { hasConflicts: false, hasError: false }; - testObject.syncBarrier.open(); - - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + let preview = await testObject.sync(await client.getResourceManifest(), true); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); @@ -676,14 +620,13 @@ suite('TestSynchronizer - Manual Sync', () => { }); }); - test('preivew -> merge -> discard -> accept', async () => { + test('preivew -> discard -> accept', async () => { await runWithFakedTimers({}, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + let preview = await testObject.sync(await client.getResourceManifest(), true); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].remoteResource); @@ -694,30 +637,13 @@ suite('TestSynchronizer - Manual Sync', () => { }); }); - test('preivew -> accept -> discard', async () => { - await runWithFakedTimers({}, async () => { - const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); - testObject.syncResult = { hasConflicts: false, hasError: false }; - testObject.syncBarrier.open(); - - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); - preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); - - assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); - assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Preview); - assertConflicts(testObject.conflicts.conflicts, []); - }); - }); - test('preivew -> accept -> discard -> accept', async () => { await runWithFakedTimers({}, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); - let preview = await testObject.preview(await client.getResourceManifest(), {}); + let preview = await testObject.sync(await client.getResourceManifest(), true); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].remoteResource); @@ -729,32 +655,13 @@ suite('TestSynchronizer - Manual Sync', () => { }); }); - test('preivew -> accept -> discard -> merge', async () => { - await runWithFakedTimers({}, async () => { - const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); - testObject.syncResult = { hasConflicts: false, hasError: false }; - testObject.syncBarrier.open(); - - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); - preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); - preview = await testObject.merge(preview!.resourcePreviews[0].remoteResource); - - assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); - assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Accepted); - assertConflicts(testObject.conflicts.conflicts, []); - }); - }); - - test('preivew -> merge -> accept -> discard', async () => { + test('preivew -> accept -> discard', async () => { await runWithFakedTimers({}, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + let preview = await testObject.sync(await client.getResourceManifest(), true); preview = await testObject.accept(preview!.resourcePreviews[0].remoteResource); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); @@ -765,29 +672,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); }); - test('preivew -> merge -> discard -> accept -> apply', async () => { - await runWithFakedTimers({}, async () => { - const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); - testObject.syncResult = { hasConflicts: false, hasError: false }; - testObject.syncBarrier.open(); - await testObject.sync(await client.getResourceManifest()); - - const expectedContent = (await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(); - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); - preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); - preview = await testObject.accept(preview!.resourcePreviews[0].localResource); - preview = await testObject.apply(false); - - assert.deepStrictEqual(testObject.status, SyncStatus.Idle); - assert.strictEqual(preview, null); - assertConflicts(testObject.conflicts.conflicts, []); - assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); - assert.strictEqual((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); - }); - }); - - test('preivew -> accept -> discard -> accept -> apply', async () => { + test('preivew -> discard -> accept -> apply', async () => { await runWithFakedTimers({}, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); testObject.syncResult = { hasConflicts: false, hasError: false }; @@ -795,8 +680,7 @@ suite('TestSynchronizer - Manual Sync', () => { await testObject.sync(await client.getResourceManifest()); const expectedContent = (await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(); - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + let preview = await testObject.sync(await client.getResourceManifest(), true); preview = await testObject.accept(preview!.resourcePreviews[0].remoteResource); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].localResource); @@ -810,53 +694,13 @@ suite('TestSynchronizer - Manual Sync', () => { }); }); - test('preivew -> accept -> discard -> merge -> apply', async () => { - await runWithFakedTimers({}, async () => { - const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); - testObject.syncResult = { hasConflicts: false, hasError: false }; - testObject.syncBarrier.open(); - await testObject.sync(await client.getResourceManifest()); - - const manifest = await client.getResourceManifest(); - const expectedContent = manifest![testObject.resource]; - let preview = await testObject.preview(manifest, {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); - preview = await testObject.accept(preview!.resourcePreviews[0].remoteResource); - preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); - preview = await testObject.merge(preview!.resourcePreviews[0].localResource); - preview = await testObject.apply(false); - - assert.deepStrictEqual(testObject.status, SyncStatus.Idle); - assert.strictEqual(preview, null); - assertConflicts(testObject.conflicts.conflicts, []); - - assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); - assert.strictEqual((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); - }); - }); - test('conflicts: preview', async () => { await runWithFakedTimers({}, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); - const preview = await testObject.preview(await client.getResourceManifest(), {}); - - assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); - assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assertConflicts(testObject.conflicts.conflicts, []); - }); - }); - - test('conflicts: preview -> merge', async () => { - await runWithFakedTimers({}, async () => { - const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); - testObject.syncResult = { hasConflicts: true, hasError: false }; - testObject.syncBarrier.open(); - - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + const preview = await testObject.sync(await client.getResourceManifest(), true); assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); assertPreviews(preview!.resourcePreviews, [testObject.localResource]); @@ -865,14 +709,13 @@ suite('TestSynchronizer - Manual Sync', () => { }); }); - test('conflicts: preview -> merge -> discard', async () => { + test('conflicts: preview -> discard', async () => { await runWithFakedTimers({}, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); - const preview = await testObject.preview(await client.getResourceManifest(), {}); - await testObject.merge(preview!.resourcePreviews[0].previewResource); + const preview = await testObject.sync(await client.getResourceManifest(), true); await testObject.discard(preview!.resourcePreviews[0].previewResource); assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); @@ -888,8 +731,7 @@ suite('TestSynchronizer - Manual Sync', () => { testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); - let preview = await testObject.preview(await client.getResourceManifest(), {}); - await testObject.merge(preview!.resourcePreviews[0].previewResource); + let preview = await testObject.sync(await client.getResourceManifest(), true); const content = await testObject.resolveContent(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, content); @@ -899,38 +741,13 @@ suite('TestSynchronizer - Manual Sync', () => { }); }); - test('conflicts: preview -> merge -> accept -> apply', async () => { - await runWithFakedTimers({}, async () => { - const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); - testObject.syncResult = { hasConflicts: false, hasError: false }; - testObject.syncBarrier.open(); - await testObject.sync(await client.getResourceManifest()); - - testObject.syncResult = { hasConflicts: true, hasError: false }; - const manifest = await client.getResourceManifest(); - const expectedContent = manifest![testObject.resource]; - let preview = await testObject.preview(manifest, {}); - - await testObject.merge(preview!.resourcePreviews[0].previewResource); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); - preview = await testObject.apply(false); - - assert.deepStrictEqual(testObject.status, SyncStatus.Idle); - assert.strictEqual(preview, null); - assertConflicts(testObject.conflicts.conflicts, []); - - assert.strictEqual((await testObject.getRemoteUserData(null)).syncData?.content, expectedContent); - assert.strictEqual((await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(), expectedContent); - }); - }); - test('conflicts: preview -> accept 2', async () => { await runWithFakedTimers({}, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); - let preview = await testObject.preview(await client.getResourceManifest(), {}); + let preview = await testObject.sync(await client.getResourceManifest(), true); const content = await testObject.resolveContent(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, content); @@ -950,7 +767,7 @@ suite('TestSynchronizer - Manual Sync', () => { testObject.syncResult = { hasConflicts: true, hasError: false }; const manifest = await client.getResourceManifest(); const expectedContent = manifest![testObject.resource]; - let preview = await testObject.preview(manifest, {}); + let preview = await testObject.sync(manifest, true); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); preview = await testObject.apply(false); @@ -964,14 +781,13 @@ suite('TestSynchronizer - Manual Sync', () => { }); }); - test('conflicts: preivew -> merge -> discard', async () => { + test('conflicts: preivew -> discard', async () => { await runWithFakedTimers({}, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + let preview = await testObject.sync(await client.getResourceManifest(), true); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); @@ -981,14 +797,13 @@ suite('TestSynchronizer - Manual Sync', () => { }); }); - test('conflicts: preivew -> merge -> discard -> accept', async () => { + test('conflicts: preivew -> discard -> accept', async () => { await runWithFakedTimers({}, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + let preview = await testObject.sync(await client.getResourceManifest(), true); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].remoteResource); @@ -999,30 +814,13 @@ suite('TestSynchronizer - Manual Sync', () => { }); }); - test('conflicts: preivew -> accept -> discard', async () => { - await runWithFakedTimers({}, async () => { - const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); - testObject.syncResult = { hasConflicts: true, hasError: false }; - testObject.syncBarrier.open(); - - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); - preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); - - assert.deepStrictEqual(testObject.status, SyncStatus.Syncing); - assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Preview); - assertConflicts(testObject.conflicts.conflicts, []); - }); - }); - test('conflicts: preivew -> accept -> discard -> accept', async () => { await runWithFakedTimers({}, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); - let preview = await testObject.preview(await client.getResourceManifest(), {}); + let preview = await testObject.sync(await client.getResourceManifest(), true); preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].remoteResource); @@ -1034,50 +832,13 @@ suite('TestSynchronizer - Manual Sync', () => { }); }); - test('conflicts: preivew -> accept -> discard -> merge', async () => { - await runWithFakedTimers({}, async () => { - const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); - testObject.syncResult = { hasConflicts: true, hasError: false }; - testObject.syncBarrier.open(); - - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); - preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); - preview = await testObject.merge(preview!.resourcePreviews[0].remoteResource); - - assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); - assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Conflict); - assertConflicts(testObject.conflicts.conflicts, [preview!.resourcePreviews[0].localResource]); - }); - }); - - test('conflicts: preivew -> merge -> discard -> merge', async () => { - await runWithFakedTimers({}, async () => { - const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); - testObject.syncResult = { hasConflicts: true, hasError: false }; - testObject.syncBarrier.open(); - - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); - preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); - preview = await testObject.merge(preview!.resourcePreviews[0].remoteResource); - - assert.deepStrictEqual(testObject.status, SyncStatus.HasConflicts); - assertPreviews(preview!.resourcePreviews, [testObject.localResource]); - assert.strictEqual(preview!.resourcePreviews[0].mergeState, MergeState.Conflict); - assertConflicts(testObject.conflicts.conflicts, [preview!.resourcePreviews[0].localResource]); - }); - }); - - test('conflicts: preivew -> merge -> accept -> discard', async () => { + test('conflicts: preivew -> accept -> discard', async () => { await runWithFakedTimers({}, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); testObject.syncResult = { hasConflicts: false, hasError: false }; testObject.syncBarrier.open(); - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + let preview = await testObject.sync(await client.getResourceManifest(), true); preview = await testObject.accept(preview!.resourcePreviews[0].remoteResource); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); @@ -1088,7 +849,7 @@ suite('TestSynchronizer - Manual Sync', () => { }); }); - test('conflicts: preivew -> merge -> discard -> accept -> apply', async () => { + test('conflicts: preivew -> discard -> accept -> apply', async () => { await runWithFakedTimers({}, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); testObject.syncResult = { hasConflicts: false, hasError: false }; @@ -1096,8 +857,7 @@ suite('TestSynchronizer - Manual Sync', () => { await testObject.sync(await client.getResourceManifest()); const expectedContent = (await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(); - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + let preview = await testObject.sync(await client.getResourceManifest(), true); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].localResource); preview = await testObject.apply(false); @@ -1118,8 +878,7 @@ suite('TestSynchronizer - Manual Sync', () => { await testObject.sync(await client.getResourceManifest()); const expectedContent = (await client.instantiationService.get(IFileService).readFile(testObject.localResource)).value.toString(); - let preview = await testObject.preview(await client.getResourceManifest(), {}); - preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); + let preview = await testObject.sync(await client.getResourceManifest(), true); preview = await testObject.accept(preview!.resourcePreviews[0].remoteResource); preview = await testObject.discard(preview!.resourcePreviews[0].previewResource); preview = await testObject.accept(preview!.resourcePreviews[0].localResource); diff --git a/src/vs/platform/userDataSync/test/common/tasksSync.test.ts b/src/vs/platform/userDataSync/test/common/tasksSync.test.ts index 26fa39aeeb2be..e875a66455da9 100644 --- a/src/vs/platform/userDataSync/test/common/tasksSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/tasksSync.test.ts @@ -540,7 +540,7 @@ suite('TasksSync', () => { await fileService.del(tasksResource); } - const preview = (await testObject.preview(await client.getResourceManifest(), {}))!; + const preview = (await testObject.sync(await client.getResourceManifest(), true))!; server.reset(); const content = await testObject.resolveContent(preview.resourcePreviews[0].remoteResource); diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index d8ed8a95c5fb5..cb2e6e8155516 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -21,7 +21,7 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { IProgress, IProgressService, IProgressStep, ProgressLocation } from '../../../../platform/progress/common/progress.js'; import { EditSessionsWorkbenchService } from './editSessionsStorageService.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; -import { UserDataSyncErrorCode, UserDataSyncStoreError, IUserDataSynchroniser } from '../../../../platform/userDataSync/common/userDataSync.js'; +import { UserDataSyncErrorCode, UserDataSyncStoreError } from '../../../../platform/userDataSync/common/userDataSync.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; import { getFileNamesMessage, IDialogService, IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js'; @@ -124,7 +124,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo private registeredCommands = new Set(); - private workspaceStateSynchronizer: IUserDataSynchroniser | undefined; + private workspaceStateSynchronizer: WorkspaceStateSynchroniser | undefined; private editSessionsStorageClient: EditSessionsStoreClient | undefined; constructor( @@ -565,7 +565,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo } } - await this.workspaceStateSynchronizer?.apply(false, {}); + await this.workspaceStateSynchronizer?.apply(); this.logService.info(`Deleting edit session with ref ${ref} after successfully applying it to current workspace...`); await this.editSessionsStorageService.delete('editSessions', ref); @@ -743,7 +743,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo } // Store contributed workspace state - await this.workspaceStateSynchronizer?.sync(null, {}); + await this.workspaceStateSynchronizer?.sync(); if (!hasEdits) { this.logService.info('Skipped storing working changes in the cloud as there are no edits to store.'); diff --git a/src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts b/src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts index dd6f614590195..0068c223d2b36 100644 --- a/src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts +++ b/src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts @@ -16,7 +16,7 @@ import { ITelemetryService } from '../../../../platform/telemetry/common/telemet import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { IUserDataProfile } from '../../../../platform/userDataProfile/common/userDataProfile.js'; import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview, ISyncResourcePreview } from '../../../../platform/userDataSync/common/abstractSynchronizer.js'; -import { IRemoteUserData, IResourceRefHandle, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSyncEnablementService, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSynchroniser, IWorkspaceState, SyncResource } from '../../../../platform/userDataSync/common/userDataSync.js'; +import { IRemoteUserData, IResourceRefHandle, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSyncEnablementService, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSynchroniser, IWorkspaceState, SyncResource, IUserDataSyncResourcePreview } from '../../../../platform/userDataSync/common/userDataSync.js'; import { EditSession, IEditSessionsStorageService } from './editSessions.js'; import { IWorkspaceIdentityService } from '../../../services/workspaces/common/workspaceIdentityService.js'; @@ -75,11 +75,11 @@ export class WorkspaceStateSynchroniser extends AbstractSynchroniser implements super({ syncResource: SyncResource.WorkspaceState, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); } - override async sync(): Promise { + override async sync(): Promise { const cancellationTokenSource = new CancellationTokenSource(); const folders = await this.workspaceIdentityService.getWorkspaceStateFolders(cancellationTokenSource.token); if (!folders.length) { - return; + return null; } // Ensure we have latest state by sending out onWillSaveState event @@ -87,7 +87,7 @@ export class WorkspaceStateSynchroniser extends AbstractSynchroniser implements const keys = this.storageService.keys(StorageScope.WORKSPACE, StorageTarget.USER); if (!keys.length) { - return; + return null; } const contributedData: IStringDictionary = {}; @@ -100,6 +100,7 @@ export class WorkspaceStateSynchroniser extends AbstractSynchroniser implements const content: IWorkspaceState = { folders, storage: contributedData, version: this.version }; await this.editSessionsStorageService.write('workspaceState', stringify(content)); + return null; } override async apply(): Promise { From 3a4f4164c7ec15e5607c47c7666c4bde99a21958 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:49:37 +0100 Subject: [PATCH 086/200] Git - fix context key for unstageSelectedRanges (#236476) --- extensions/git/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 08349fd478371..d63027619d183 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1054,7 +1054,7 @@ }, { "command": "git.unstageSelectedRanges", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && isInDiffRightEditor && resourceScheme == git" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && resourceScheme == git" }, { "command": "git.clean", From a02cb4218adce96301f7561d7ce585cdeb84598f Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 18 Dec 2024 09:10:16 -0600 Subject: [PATCH 087/200] add `when` for terminal chat keybinding (#236480) fix #236474 --- .../terminalContrib/chat/browser/terminalChatActions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index deeb48f48c792..0d6fdd3536166 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -224,7 +224,8 @@ registerActiveXtermAction({ ), keybinding: { weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyCode.KeyR + primary: KeyMod.CtrlCmd | KeyCode.KeyR, + when: TerminalChatContextKeys.focused }, menu: { id: MENU_TERMINAL_CHAT_WIDGET_STATUS, From cec5112d1172e69cee13d729f5bc21ceaad36519 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 18 Dec 2024 16:40:44 +0100 Subject: [PATCH 088/200] Fixes https://github.com/microsoft/vscode-copilot/issues/9818 (#236486) --- .../browser/controller/inlineCompletionsController.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index f3720436c2f65..7c7e449571978 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -181,14 +181,16 @@ export class InlineCompletionsController extends Disposable { return; } - if (this.model.get()?.inlineEditAvailable.get()) { - // dont hide inline edits on blur + const model = this.model.get(); + if (!model) { return; } + if (model.state.get()?.inlineCompletion?.request.isExplicitRequest && model.inlineEditAvailable.get()) { + // dont hide inline edits on blur when requested explicitly return; } transaction(tx => { /** @description InlineCompletionsController.onDidBlurEditorWidget */ - this.model.get()?.stop('automatic', tx); + model.stop('automatic', tx); }); })); From 929ce7c1fa6bd6acccf703b5b1c491659b63ef11 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 18 Dec 2024 16:45:47 +0100 Subject: [PATCH 089/200] fixes a random bunch of leaking disposables (#236487) Adds (disabled for now) disposable tracker that is based on the finalization registry, gist is that every GC'ed disposable that isn't disposed is a bug. --- .../ui/breadcrumbs/breadcrumbsWidget.ts | 1 + src/vs/base/common/async.ts | 2 +- src/vs/base/common/lifecycle.ts | 28 +++++++++++++++++++ .../services/hoverService/hoverService.ts | 2 +- .../codeAction/browser/codeActionModel.ts | 1 + .../chat/browser/actions/chatActions.ts | 4 +-- .../debug/common/debugContentProvider.ts | 9 ++++-- .../browser/performance.contribution.ts | 25 ++++++++++++++++- .../browser/searchTreeModel/fileMatch.ts | 12 ++++---- .../common/notificationService.ts | 2 +- .../progress/browser/progressService.ts | 5 +++- 11 files changed, 75 insertions(+), 16 deletions(-) diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index b87e883bd3a09..a4cb3361f9df2 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -282,6 +282,7 @@ export class BreadcrumbsWidget { removed = this._items.splice(prefix, this._items.length - prefix, ...items.slice(prefix)); this._render(prefix); dispose(removed); + dispose(items.slice(0, prefix)); this._focus(-1, undefined); } catch (e) { const newError = new Error(`BreadcrumbsItem#setItems: newItems: ${items.length}, prefix: ${prefix}, removed: ${removed.length}`); diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index bd76ba77e4ad6..84feeb7770480 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -557,7 +557,7 @@ export function disposableTimeout(handler: () => void, timeout = 0, store?: Disp }, timeout); const disposable = toDisposable(() => { clearTimeout(timer); - store?.deleteAndLeak(disposable); + store?.delete(disposable); }); store?.add(disposable); return disposable; diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 4cc2d172bf4f6..e04e62f76d38d 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -44,6 +44,34 @@ export interface IDisposableTracker { markAsSingleton(disposable: IDisposable): void; } +export class GCBasedDisposableTracker implements IDisposableTracker { + + private readonly _registry = new FinalizationRegistry(heldValue => { + console.warn(`[LEAKED DISPOSABLE] ${heldValue}`); + }); + + trackDisposable(disposable: IDisposable): void { + const stack = new Error('CREATED via:').stack!; + this._registry.register(disposable, stack, disposable); + } + + setParent(child: IDisposable, parent: IDisposable | null): void { + if (parent) { + this._registry.unregister(child); + } else { + this.trackDisposable(child); + } + } + + markAsDisposed(disposable: IDisposable): void { + this._registry.unregister(disposable); + } + + markAsSingleton(disposable: IDisposable): void { + this._registry.unregister(disposable); + } +} + export interface DisposableInfo { value: IDisposable; source: string | null; diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index b4164c04d1d9f..873ece14a4f8c 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -51,7 +51,7 @@ export class HoverService extends Disposable implements IHoverService { ) { super(); - contextMenuService.onDidShowContextMenu(() => this.hideHover()); + this._register(contextMenuService.onDidShowContextMenu(() => this.hideHover())); this._contextViewHandler = this._register(new ContextViewHandler(this._layoutService)); } diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts b/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts index a909d559ed83e..10381ff4ffcea 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts @@ -235,6 +235,7 @@ export class CodeActionModel extends Disposable { const codeActionSet = await getCodeActions(this._registry, model, trigger.selection, trigger.trigger, Progress.None, token); const allCodeActions = [...codeActionSet.allActions]; if (token.isCancellationRequested) { + codeActionSet.dispose(); return emptyCodeActionSet; } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 4cb15f40d68e7..a0bb888eb80f1 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -561,7 +561,7 @@ export class ChatCommandCenterRendering extends Disposable implements IWorkbench const contextKeySet = new Set([ChatContextKeys.Setup.signedOut.key]); - actionViewItemService.register(MenuId.CommandCenter, MenuId.ChatCommandCenter, (action, options) => { + this._store.add(actionViewItemService.register(MenuId.CommandCenter, MenuId.ChatCommandCenter, (action, options) => { if (!(action instanceof SubmenuItemAction)) { return undefined; } @@ -607,6 +607,6 @@ export class ChatCommandCenterRendering extends Disposable implements IWorkbench agentService.onDidChangeAgents, chatQuotasService.onDidChangeQuotas, Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(contextKeySet)) - )); + ))); } } diff --git a/src/vs/workbench/contrib/debug/common/debugContentProvider.ts b/src/vs/workbench/contrib/debug/common/debugContentProvider.ts index 935d2c3db2d89..5ad05e1cb7db4 100644 --- a/src/vs/workbench/contrib/debug/common/debugContentProvider.ts +++ b/src/vs/workbench/contrib/debug/common/debugContentProvider.ts @@ -19,6 +19,7 @@ import { Range } from '../../../../editor/common/core/range.js'; import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js'; import { ErrorNoTelemetry } from '../../../../base/common/errors.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; /** * Debug URI format @@ -33,7 +34,7 @@ import { ErrorNoTelemetry } from '../../../../base/common/errors.js'; * the arbitrary_path and the session id are encoded with 'encodeURIComponent' * */ -export class DebugContentProvider implements IWorkbenchContribution, ITextModelContentProvider { +export class DebugContentProvider extends Disposable implements IWorkbenchContribution, ITextModelContentProvider { private static INSTANCE: DebugContentProvider; @@ -46,12 +47,14 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC @ILanguageService private readonly languageService: ILanguageService, @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService ) { - textModelResolverService.registerTextModelContentProvider(DEBUG_SCHEME, this); + super(); + this._store.add(textModelResolverService.registerTextModelContentProvider(DEBUG_SCHEME, this)); DebugContentProvider.INSTANCE = this; } - dispose(): void { + override dispose(): void { this.pendingUpdates.forEach(cancellationSource => cancellationSource.dispose()); + super.dispose(); } provideTextContent(resource: uri): Promise | null { diff --git a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts index a4b1a39c3e454..bd69320c35840 100644 --- a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts +++ b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts @@ -9,13 +9,15 @@ import { IInstantiationService, ServicesAccessor } from '../../../../platform/in import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; -import { Extensions, IWorkbenchContributionsRegistry, registerWorkbenchContribution2 } from '../../../common/contributions.js'; +import { Extensions, IWorkbenchContributionsRegistry, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; import { EditorExtensions, IEditorSerializer, IEditorFactoryRegistry } from '../../../common/editor.js'; import { PerfviewContrib, PerfviewInput } from './perfviewEditor.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { InstantiationService, Trace } from '../../../../platform/instantiation/common/instantiationService.js'; import { EventProfiling } from '../../../../base/common/event.js'; import { InputLatencyContrib } from './inputLatencyContrib.js'; +import { IEnvironmentService } from '../../../../platform/environment/common/environment.js'; +import { GCBasedDisposableTracker, setDisposableTracker } from '../../../../base/common/lifecycle.js'; // -- startup performance view @@ -136,3 +138,24 @@ Registry.as(Extensions.Workbench).registerWorkb InputLatencyContrib, LifecyclePhase.Eventually ); + + +// -- track leaking disposables, those that get GC'ed before having been disposed + +// this is currently disabled because there is too many leaks and some false positives, e.g disposables from registers +// like MenuRegistry, CommandsRegistery etc should be marked as singleton + +const _enableLeakDetection = false + // || Boolean("true") // done "weirdly" so that a lint warning prevents you from pushing this + ; + +class DisposableTracking { + static readonly Id = 'perf.disposableTracking'; + constructor(@IEnvironmentService envService: IEnvironmentService) { + if (!envService.isBuilt && _enableLeakDetection) { + setDisposableTracker(new GCBasedDisposableTracker()); + } + } +} + +registerWorkbenchContribution2(DisposableTracking.Id, DisposableTracking, WorkbenchPhase.Eventually); diff --git a/src/vs/workbench/contrib/search/browser/searchTreeModel/fileMatch.ts b/src/vs/workbench/contrib/search/browser/searchTreeModel/fileMatch.ts index 1b5d2fa717bb0..dfaf589263b15 100644 --- a/src/vs/workbench/contrib/search/browser/searchTreeModel/fileMatch.ts +++ b/src/vs/workbench/contrib/search/browser/searchTreeModel/fileMatch.ts @@ -5,7 +5,7 @@ import { RunOnceScheduler } from '../../../../../base/common/async.js'; import { Lazy } from '../../../../../base/common/lazy.js'; -import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { themeColorFromId } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { TrackedRangeStickiness, MinimapPosition, ITextModel, FindMatch, IModelDeltaDecoration } from '../../../../../editor/common/model.js'; @@ -69,7 +69,7 @@ export class FileMatchImpl extends Disposable implements ISearchTreeFileMatch { protected _resource: URI; private _fileStat?: IFileStatWithPartialMetadata; private _model: ITextModel | null = null; - private _modelListener: IDisposable | null = null; + private _modelListener: DisposableStore | null = null; protected _textMatches: Map; private _removedTextMatches: Set; @@ -132,10 +132,11 @@ export class FileMatchImpl extends Disposable implements ISearchTreeFileMatch { } bindModel(model: ITextModel): void { this._model = model; - this._modelListener = this._model.onDidChangeContent(() => { + this._modelListener = new DisposableStore(); + this._modelListener.add(this._model.onDidChangeContent(() => { this._updateScheduler.schedule(); - }); - this._model.onWillDispose(() => this.onModelWillDispose()); + })); + this._modelListener.add(this._model.onWillDispose(() => this.onModelWillDispose())); this.updateHighlights(); } @@ -360,4 +361,3 @@ export class FileMatchImpl extends Disposable implements ISearchTreeFileMatch { //#endregion } - diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index 151edfccf9fb1..f557bc098c133 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -272,7 +272,6 @@ export class NotificationService extends Disposable implements INotificationServ } prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle { - const toDispose = new DisposableStore(); // Handle neverShowAgain option accordingly if (options?.neverShowAgain) { @@ -300,6 +299,7 @@ export class NotificationService extends Disposable implements INotificationServ } let choiceClicked = false; + const toDispose = new DisposableStore(); // Convert choices into primary/secondary actions diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 872300d3937bc..73b4c71daba12 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -356,7 +356,10 @@ export class ProgressService extends Disposable implements IProgressService { } // Clear upon dispose - Event.once(notification.onDidClose)(() => notificationDisposables.dispose()); + Event.once(notification.onDidClose)(() => { + notificationDisposables.dispose(); + dispose(windowProgressDisposable); + }); return notification; }; From 148a1ca4bd890aa4743327988070da0546180dd2 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 18 Dec 2024 17:41:28 +0100 Subject: [PATCH 090/200] TreeView: MaxCallStackError - Nesting (#236493) Part of #233056 --- src/vs/base/browser/ui/list/listView.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index e149029df4f5b..00a19352e91ad 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -9,7 +9,7 @@ import { DomEmitter } from '../../event.js'; import { IMouseWheelEvent } from '../../mouseEvent.js'; import { EventType as TouchEventType, Gesture, GestureEvent } from '../../touch.js'; import { SmoothScrollableElement } from '../scrollbar/scrollableElement.js'; -import { distinct, equals } from '../../../common/arrays.js'; +import { distinct, equals, splice } from '../../../common/arrays.js'; import { Delayer, disposableTimeout } from '../../../common/async.js'; import { memoize } from '../../../common/decorators.js'; import { Emitter, Event, IValueWithChangeEvent } from '../../../common/event.js'; @@ -643,7 +643,7 @@ export class ListView implements IListView { this.items = inserted; } else { this.rangeMap.splice(start, deleteCount, inserted); - deleted = this.items.splice(start, deleteCount, ...inserted); + deleted = splice(this.items, start, deleteCount, inserted); } const delta = elements.length - deleteCount; From 7d0efabd3b29e465be0c76c50ffedec391de3379 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 18 Dec 2024 17:47:19 +0100 Subject: [PATCH 091/200] Adds single observable logging (#236481) * Adds single observable logging * Improved observePosition * Fixes CI * Fixes tests --- src/vs/base/common/observableInternal/base.ts | 13 +++- .../base/common/observableInternal/derived.ts | 10 +++ .../observableInternal/lazyObservableValue.ts | 7 +- .../base/common/observableInternal/logging.ts | 69 +++++++++++++++---- src/vs/base/test/common/observable.test.ts | 10 +-- src/vs/editor/browser/observableCodeEditor.ts | 6 +- 6 files changed, 89 insertions(+), 26 deletions(-) diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index 3ce43ead2aea6..c28e773b26886 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -6,7 +6,7 @@ import { DebugNameData, DebugOwner, getFunctionName } from './debugName.js'; import { DisposableStore, EqualityComparer, IDisposable, strictEquals } from './commonFacade/deps.js'; import type { derivedOpts } from './derived.js'; -import { getLogger } from './logging.js'; +import { getLogger, logObservable } from './logging.js'; import { keepObserved, recomputeInitiallyAndOnChange } from './utils.js'; /** @@ -67,6 +67,12 @@ export interface IObservable { flatten(this: IObservable>): IObservable; + /** + * ONLY FOR DEBUGGING! + * Logs computations of this derived. + */ + log(): IObservable; + /** * Makes sure this value is computed eagerly. */ @@ -233,6 +239,11 @@ export abstract class ConvenientObservable implements IObservable { + logObservable(this); + return this; + } + /** * @sealed * Converts an observable of an observable value into a direct observable of the value. diff --git a/src/vs/base/common/observableInternal/derived.ts b/src/vs/base/common/observableInternal/derived.ts index 80cc9a721247e..ba018041799d9 100644 --- a/src/vs/base/common/observableInternal/derived.ts +++ b/src/vs/base/common/observableInternal/derived.ts @@ -459,6 +459,16 @@ export class Derived extends BaseObservable im } super.removeObserver(observer); } + + public override log(): IObservable { + if (!getLogger()) { + super.log(); + getLogger()?.handleDerivedCreated(this); + } else { + super.log(); + } + return this; + } } diff --git a/src/vs/base/common/observableInternal/lazyObservableValue.ts b/src/vs/base/common/observableInternal/lazyObservableValue.ts index 8a3f63c05d7af..6c0f85aa87675 100644 --- a/src/vs/base/common/observableInternal/lazyObservableValue.ts +++ b/src/vs/base/common/observableInternal/lazyObservableValue.ts @@ -6,6 +6,7 @@ import { EqualityComparer } from './commonFacade/deps.js'; import { BaseObservable, IObserver, ISettableObservable, ITransaction, TransactionImpl } from './base.js'; import { DebugNameData } from './debugName.js'; +import { getLogger } from './logging.js'; /** * Holds off updating observers until the value is actually read. @@ -42,13 +43,15 @@ export class LazyObservableValue this._isUpToDate = true; if (this._deltas.length > 0) { - for (const observer of this.observers) { - for (const change of this._deltas) { + for (const change of this._deltas) { + getLogger()?.handleObservableChanged(this, { change, didChange: true, oldValue: '(unknown)', newValue: this._value, hadValue: true }); + for (const observer of this.observers) { observer.handleChange(this, change); } } this._deltas.length = 0; } else { + getLogger()?.handleObservableChanged(this, { change: undefined, didChange: true, oldValue: '(unknown)', newValue: this._value, hadValue: true }); for (const observer of this.observers) { observer.handleChange(this, undefined); } diff --git a/src/vs/base/common/observableInternal/logging.ts b/src/vs/base/common/observableInternal/logging.ts index 0de37a858cb72..0c343b548e47d 100644 --- a/src/vs/base/common/observableInternal/logging.ts +++ b/src/vs/base/common/observableInternal/logging.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { AutorunObserver } from './autorun.js'; -import { IObservable, ObservableValue, TransactionImpl } from './base.js'; +import { IObservable, TransactionImpl } from './base.js'; import { Derived } from './derived.js'; import { FromEventObservable } from './utils.js'; @@ -18,6 +18,18 @@ export function getLogger(): IObservableLogger | undefined { return globalObservableLogger; } +export function logObservable(obs: IObservable): void { + if (!globalObservableLogger) { + const l = new ConsoleObservableLogger(); + l.addFilteredObj(obs); + setLogger(l); + } else { + if (globalObservableLogger instanceof ConsoleObservableLogger) { + (globalObservableLogger as ConsoleObservableLogger).addFilteredObj(obs); + } + } +} + interface IChangeInformation { oldValue: unknown; newValue: unknown; @@ -27,7 +39,7 @@ interface IChangeInformation { } export interface IObservableLogger { - handleObservableChanged(observable: ObservableValue, info: IChangeInformation): void; + handleObservableChanged(observable: IObservable, info: IChangeInformation): void; handleFromEventObservableTriggered(observable: FromEventObservable, info: IChangeInformation): void; handleAutorunCreated(autorun: AutorunObserver): void; @@ -44,6 +56,19 @@ export interface IObservableLogger { export class ConsoleObservableLogger implements IObservableLogger { private indentation = 0; + private _filteredObjects: Set | undefined; + + public addFilteredObj(obj: unknown): void { + if (!this._filteredObjects) { + this._filteredObjects = new Set(); + } + this._filteredObjects.add(obj); + } + + private _isIncluded(obj: unknown): boolean { + return this._filteredObjects?.has(obj) ?? true; + } + private textToConsoleArgs(text: ConsoleText): unknown[] { return consoleTextToArgs([ normalText(repeat('| ', this.indentation)), @@ -77,6 +102,7 @@ export class ConsoleObservableLogger implements IObservableLogger { } handleObservableChanged(observable: IObservable, info: IChangeInformation): void { + if (!this._isIncluded(observable)) { return; } console.log(...this.textToConsoleArgs([ formatKind('observable value changed'), styled(observable.debugName, { color: 'BlueViolet' }), @@ -130,7 +156,10 @@ export class ConsoleObservableLogger implements IObservableLogger { } handleDerivedRecomputed(derived: Derived, info: IChangeInformation): void { - const changedObservables = this.changedObservablesSets.get(derived)!; + if (!this._isIncluded(derived)) { return; } + + const changedObservables = this.changedObservablesSets.get(derived); + if (!changedObservables) { return; } console.log(...this.textToConsoleArgs([ formatKind('derived recomputed'), styled(derived.debugName, { color: 'BlueViolet' }), @@ -142,6 +171,8 @@ export class ConsoleObservableLogger implements IObservableLogger { } handleFromEventObservableTriggered(observable: FromEventObservable, info: IChangeInformation): void { + if (!this._isIncluded(observable)) { return; } + console.log(...this.textToConsoleArgs([ formatKind('observable from event triggered'), styled(observable.debugName, { color: 'BlueViolet' }), @@ -151,6 +182,8 @@ export class ConsoleObservableLogger implements IObservableLogger { } handleAutorunCreated(autorun: AutorunObserver): void { + if (!this._isIncluded(autorun)) { return; } + const existingHandleChange = autorun.handleChange; this.changedObservablesSets.set(autorun, new Set()); autorun.handleChange = (observable, change) => { @@ -160,13 +193,17 @@ export class ConsoleObservableLogger implements IObservableLogger { } handleAutorunTriggered(autorun: AutorunObserver): void { - const changedObservables = this.changedObservablesSets.get(autorun)!; - console.log(...this.textToConsoleArgs([ - formatKind('autorun'), - styled(autorun.debugName, { color: 'BlueViolet' }), - this.formatChanges(changedObservables), - { data: [{ fn: autorun._debugNameData.referenceFn ?? autorun._runFn }] } - ])); + const changedObservables = this.changedObservablesSets.get(autorun); + if (!changedObservables) { return; } + + if (this._isIncluded(autorun)) { + console.log(...this.textToConsoleArgs([ + formatKind('autorun'), + styled(autorun.debugName, { color: 'BlueViolet' }), + this.formatChanges(changedObservables), + { data: [{ fn: autorun._debugNameData.referenceFn ?? autorun._runFn }] } + ])); + } changedObservables.clear(); this.indentation++; } @@ -180,11 +217,13 @@ export class ConsoleObservableLogger implements IObservableLogger { if (transactionName === undefined) { transactionName = ''; } - console.log(...this.textToConsoleArgs([ - formatKind('transaction'), - styled(transactionName, { color: 'BlueViolet' }), - { data: [{ fn: transaction._fn }] } - ])); + if (this._isIncluded(transaction)) { + console.log(...this.textToConsoleArgs([ + formatKind('transaction'), + styled(transactionName, { color: 'BlueViolet' }), + { data: [{ fn: transaction._fn }] } + ])); + } this.indentation++; } diff --git a/src/vs/base/test/common/observable.test.ts b/src/vs/base/test/common/observable.test.ts index 2f89792432324..3d7ed472fcdb1 100644 --- a/src/vs/base/test/common/observable.test.ts +++ b/src/vs/base/test/common/observable.test.ts @@ -1507,21 +1507,21 @@ export class LoggingObservableValue implements ISettableObservable { private value: T; - constructor(public readonly debugName: string, initialValue: T, private readonly log: Log) { + constructor(public readonly debugName: string, initialValue: T, private readonly logger: Log) { super(); this.value = initialValue; } protected override onFirstObserverAdded(): void { - this.log.log(`${this.debugName}.firstObserverAdded`); + this.logger.log(`${this.debugName}.firstObserverAdded`); } protected override onLastObserverRemoved(): void { - this.log.log(`${this.debugName}.lastObserverRemoved`); + this.logger.log(`${this.debugName}.lastObserverRemoved`); } public get(): T { - this.log.log(`${this.debugName}.get`); + this.logger.log(`${this.debugName}.get`); return this.value; } @@ -1537,7 +1537,7 @@ export class LoggingObservableValue return; } - this.log.log(`${this.debugName}.set (value ${value})`); + this.logger.log(`${this.debugName}.set (value ${value})`); this.value = value; diff --git a/src/vs/editor/browser/observableCodeEditor.ts b/src/vs/editor/browser/observableCodeEditor.ts index 404f10dbe4605..781a9895212f6 100644 --- a/src/vs/editor/browser/observableCodeEditor.ts +++ b/src/vs/editor/browser/observableCodeEditor.ts @@ -266,13 +266,13 @@ export class ObservableCodeEditor extends Disposable { } public observePosition(position: IObservable, store: DisposableStore): IObservable { - const result = observableValueOpts({ owner: this, equalsFn: equalsIfDefined(Point.equals) }, new Point(0, 0)); + let pos = position.get(); + const result = observableValueOpts({ owner: this, debugName: () => `topLeftOfPosition${pos?.toString()}`, equalsFn: equalsIfDefined(Point.equals) }, new Point(0, 0)); const contentWidgetId = `observablePositionWidget` + (this._widgetCounter++); const domNode = document.createElement('div'); const w: IContentWidget = { getDomNode: () => domNode, getPosition: () => { - const pos = position.get(); return pos ? { preference: [ContentWidgetPositionPreference.EXACT], position: position.get() } : null; }, getId: () => contentWidgetId, @@ -283,7 +283,7 @@ export class ObservableCodeEditor extends Disposable { }; this.editor.addContentWidget(w); store.add(autorun(reader => { - position.read(reader); + pos = position.read(reader); this.editor.layoutContentWidget(w); })); store.add(toDisposable(() => { From aaa5982ec9d269d9c63d0a5e762c7c307b8e5033 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 18 Dec 2024 09:50:52 -0800 Subject: [PATCH 092/200] debug: fix underflow/overflow in breakpoint edit widget (#236428) Fixes #233819 --- src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts | 1 + .../workbench/contrib/debug/browser/media/breakpointWidget.css | 1 + 2 files changed, 2 insertions(+) diff --git a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts index a6a120b040ab2..d40ff7189c585 100644 --- a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts +++ b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts @@ -180,6 +180,7 @@ export class GlyphHoverWidget extends Disposable implements IOverlayWidget, IHov const left = editorLayout.glyphMarginLeft + editorLayout.glyphMarginWidth + (laneOrLine === 'lineNo' ? editorLayout.lineNumbersWidth : 0); this._hover.containerDomNode.style.left = `${left}px`; this._hover.containerDomNode.style.top = `${Math.max(Math.round(top), 0)}px`; + this._hover.containerDomNode.style.zIndex = '11'; // 1 more than the zone widget at 10 (#233819) } private _onMouseLeave(e: MouseEvent): void { diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css index 4bbf07f03c77d..27b11b0a3923d 100644 --- a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css @@ -6,6 +6,7 @@ .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget { display: flex; border-color: #007ACC; + background: var(--vscode-editor-background); .breakpoint-select-container { display: flex; From 4fcae8834d70e4beadbeceeedd62d1e12484fe3e Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 18 Dec 2024 12:38:58 -0600 Subject: [PATCH 093/200] do not show no suggestions widget unless it was explicitly invoked (#236505) --- .../browser/terminal.suggest.contribution.ts | 2 +- .../suggest/browser/terminalSuggestAddon.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts index 82b9dc7d51491..96676a5a12c13 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts @@ -192,7 +192,7 @@ registerActiveInstanceAction({ weight: KeybindingWeight.WorkbenchContrib + 1, when: ContextKeyExpr.and(TerminalContextKeys.focus, TerminalContextKeys.terminalShellIntegrationEnabled, ContextKeyExpr.equals(`config.${TerminalSuggestSettingId.Enabled}`, true)) }, - run: (activeInstance) => TerminalSuggestContribution.get(activeInstance)?.addon?.requestCompletions() + run: (activeInstance) => TerminalSuggestContribution.get(activeInstance)?.addon?.requestCompletions(true) }); registerActiveInstanceAction({ diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index 331d36a2e58af..9f173cfc15a68 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -129,7 +129,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest })); } - private async _handleCompletionProviders(terminal: Terminal | undefined, token: CancellationToken, triggerCharacter?: boolean): Promise { + private async _handleCompletionProviders(terminal: Terminal | undefined, token: CancellationToken, explicitlyInvoked?: boolean): Promise { // Nothing to handle if the terminal is not attached if (!terminal?.element || !this._enableWidget || !this._promptInputModel) { return; @@ -156,7 +156,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest await this._extensionService.activateByEvent('onTerminalCompletionsRequested'); } - const providedCompletions = await this._terminalCompletionService.provideCompletions(this._promptInputModel.prefix, this._promptInputModel.cursorIndex, this._shellType, token, triggerCharacter, doNotRequestExtensionCompletions); + const providedCompletions = await this._terminalCompletionService.provideCompletions(this._promptInputModel.prefix, this._promptInputModel.cursorIndex, this._shellType, token, doNotRequestExtensionCompletions); if (!providedCompletions?.length || token.isCancellationRequested) { return; } @@ -220,7 +220,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest if (token.isCancellationRequested) { return; } - this._showCompletions(model); + this._showCompletions(model, explicitlyInvoked); } setContainerWithOverflow(container: HTMLElement): void { @@ -231,7 +231,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest this._screen = screen; } - async requestCompletions(triggerCharacter?: boolean): Promise { + async requestCompletions(explicitlyInvoked?: boolean): Promise { if (!this._promptInputModel) { return; } @@ -245,7 +245,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest } this._cancellationTokenSource = new CancellationTokenSource(); const token = this._cancellationTokenSource.token; - await this._handleCompletionProviders(this._terminal, token, triggerCharacter); + await this._handleCompletionProviders(this._terminal, token, explicitlyInvoked); } private _sync(promptInputState: IPromptInputModelState): void { @@ -292,7 +292,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest } for (const char of provider.triggerCharacters) { if (prefix?.endsWith(char)) { - this.requestCompletions(true); + this.requestCompletions(); sent = true; break; } @@ -359,13 +359,13 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest }; } - private _showCompletions(model: SimpleCompletionModel): void { + private _showCompletions(model: SimpleCompletionModel, explicitlyInvoked?: boolean): void { if (!this._terminal?.element) { return; } const suggestWidget = this._ensureSuggestWidget(this._terminal); suggestWidget.setCompletionModel(model); - if (!this._promptInputModel) { + if (!this._promptInputModel || !explicitlyInvoked && model.items.length === 0) { return; } this._model = model; From 4c3f5de78914fd38b4e3c3d46ccd7f777e233c39 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 18 Dec 2024 10:40:34 -0800 Subject: [PATCH 094/200] debug: make ctrl+c copy value(s) in debug views (#236501) Not sure how this ever worked, but it was reported as a bug, and this makes it work. Fixes #232767 --- .../debug/browser/debug.contribution.ts | 12 +++++++ .../contrib/debug/browser/variablesView.ts | 35 ++++++++++++++++--- .../debug/browser/watchExpressionsView.ts | 8 +++-- .../workbench/contrib/debug/common/debug.ts | 5 +++ 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 7c501db354ffb..6033b0cf9b534 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -63,6 +63,8 @@ import { ReplAccessibilityHelp } from './replAccessibilityHelp.js'; import { ReplAccessibilityAnnouncer } from '../common/replAccessibilityAnnouncer.js'; import { RunAndDebugAccessibilityHelp } from './runAndDebugAccessibilityHelp.js'; import { DebugWatchAccessibilityAnnouncer } from '../common/debugAccessibilityAnnouncer.js'; +import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { FocusedViewContext } from '../../../common/contextkeys.js'; const debugCategory = nls.localize('debugCategory', "Debug"); registerColors(); @@ -206,6 +208,16 @@ registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_WATCH_EXPRESSIONS_COM registerDebugViewMenuItem(MenuId.NotebookVariablesContext, COPY_NOTEBOOK_VARIABLE_VALUE_ID, COPY_NOTEBOOK_VARIABLE_VALUE_LABEL, 20, CONTEXT_VARIABLE_VALUE); +KeybindingsRegistry.registerKeybindingRule({ + id: COPY_VALUE_ID, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.or( + FocusedViewContext.isEqualTo(WATCH_VIEW_ID), + FocusedViewContext.isEqualTo(VARIABLES_VIEW_ID), + ), + primary: KeyMod.CtrlCmd | KeyCode.KeyC +}); + // Touch Bar if (isMacintosh) { diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 222e0a7b57b65..16d53c4f87ee0 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -40,8 +40,9 @@ import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.j 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 { IViewsService } from '../../../services/views/common/viewsService.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 { 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, IDebugViewWithVariables, IExpression, IScope, IStackFrame, IViewModel, VARIABLES_VIEW_ID, WATCH_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'; @@ -61,7 +62,7 @@ interface IVariablesContext { variable: DebugProtocol.Variable; } -export class VariablesView extends ViewPane { +export class VariablesView extends ViewPane implements IDebugViewWithVariables { private updateTreeScheduler: RunOnceScheduler; private needsRefresh = false; @@ -69,6 +70,10 @@ export class VariablesView extends ViewPane { private savedViewState = new Map(); private autoExpandedScopes = new Set(); + public get treeSelection() { + return this.tree.getSelection(); + } + constructor( options: IViewletViewOptions, @IContextMenuService contextMenuService: IContextMenuService, @@ -653,12 +658,34 @@ 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 | undefined, ctx?: (Variable | Expression)[]) => { + if (!arg) { + const viewService = accessor.get(IViewsService); + const view = viewService.getActiveViewWithId(WATCH_VIEW_ID) || viewService.getActiveViewWithId(VARIABLES_VIEW_ID); + if (view) { + + } + } const debugService = accessor.get(IDebugService); const clipboardService = accessor.get(IClipboardService); let elementContext = ''; let elements: (Variable | Expression)[]; - if (arg instanceof Variable || arg instanceof Expression) { + if (!arg) { + const viewService = accessor.get(IViewsService); + const focusedView = viewService.getFocusedView(); + let view: IDebugViewWithVariables | null | undefined; + if (focusedView?.id === WATCH_VIEW_ID) { + view = viewService.getActiveViewWithId(WATCH_VIEW_ID); + elementContext = 'watch'; + } else if (focusedView?.id === VARIABLES_VIEW_ID) { + view = viewService.getActiveViewWithId(VARIABLES_VIEW_ID); + elementContext = 'variables'; + } + if (!view) { + return; + } + elements = view.treeSelection.filter(e => e instanceof Expression || e instanceof Variable); + } else if (arg instanceof Variable || arg instanceof Expression) { elementContext = 'watch'; elements = ctx ? ctx : []; } else { diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index b1b75a73bc0fc..b325c138b0a35 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -29,7 +29,7 @@ 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 { CONTEXT_CAN_VIEW_MEMORY, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_WATCH_EXPRESSIONS_EXIST, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_WATCH_ITEM_TYPE, IDebugConfiguration, IDebugService, IDebugViewWithVariables, 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'; @@ -40,7 +40,7 @@ const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; let ignoreViewUpdates = false; let useCachedEvaluation = false; -export class WatchExpressionsView extends ViewPane { +export class WatchExpressionsView extends ViewPane implements IDebugViewWithVariables { private watchExpressionsUpdatedScheduler: RunOnceScheduler; private needsRefresh = false; @@ -51,6 +51,10 @@ export class WatchExpressionsView extends ViewPane { private menu: IMenu; private expressionRenderer: DebugExpressionRenderer; + public get treeSelection() { + return this.tree.getSelection(); + } + constructor( options: IViewletViewOptions, @IContextMenuService contextMenuService: IContextMenuService, diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index d5387b10408ec..c05015544729b 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -29,6 +29,7 @@ import { Source } from './debugSource.js'; import { ITaskIdentifier } from '../../tasks/common/tasks.js'; import { LiveTestResult } from '../../testing/common/testResult.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { IView } from '../../../common/views.js'; export const VIEWLET_ID = 'workbench.view.debug'; @@ -115,6 +116,10 @@ export const INTERNAL_CONSOLE_OPTIONS_SCHEMA = { description: nls.localize('internalConsoleOptions', "Controls when the internal Debug Console should open.") }; +export interface IDebugViewWithVariables extends IView { + readonly treeSelection: IExpression[]; +} + // raw export interface IRawModelUpdate { From d6d5ebe048e65abd1377b76bdb21935c9bd5d283 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Wed, 18 Dec 2024 10:49:25 -0800 Subject: [PATCH 095/200] Splitting cells should maintain the current edit state (#236507) * split cell command should be in editing mode * stay in preview if splitting from that state * writing the PR description made me realize that this was way simpler --- .../notebook/browser/contrib/cellCommands/cellCommands.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts index 261c80215623b..2abc28493e916 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts @@ -205,6 +205,8 @@ registerAction2(class extends NotebookCellAction { ], { quotableLabel: 'Split Notebook Cell' } ); + + context.notebookEditor.cellAt(index + 1)?.updateEditState(cell.getEditState(), 'splitCell'); } } } From 91581cab6f837fb31161322a910f4c414aa40300 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 18 Dec 2024 12:53:00 -0600 Subject: [PATCH 096/200] focus debug console when it becomes visible (#236502) fix #236499 --- src/vs/workbench/contrib/debug/browser/repl.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index d09086152d66d..e9ffbe5a57c2b 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -214,6 +214,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { this.tree?.updateChildren(undefined, true, false); this.onDidStyleChange(); } + this.focus(); } })); this._register(this.configurationService.onDidChangeConfiguration(e => { From 7f9c7a41873b88861744d06aed33d2dbcaa3a92e Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 18 Dec 2024 11:03:57 -0800 Subject: [PATCH 097/200] testing: avoid profiles dropdown when there's a single profile (#236509) Fixes #232767 --- .../testing/browser/testingExplorerView.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 5e84db11b6af9..dea0b909d049e 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -346,6 +346,7 @@ export class TestingExplorerView extends ViewPane { const profileActions: IAction[] = []; let participatingGroups = 0; + let participatingProfiles = 0; let hasConfigurable = false; const defaults = this.testProfileService.getGroupDefaultProfiles(group); for (const { profiles, controller } of this.testProfileService.all()) { @@ -363,6 +364,7 @@ export class TestingExplorerView extends ViewPane { } hasConfigurable = hasConfigurable || profile.hasConfigurationHandler; + participatingProfiles++; profileActions.push(new Action( `${controller.id}.${profile.profileId}`, defaults.includes(profile) ? localize('defaultTestProfile', '{0} (Default)', profile.label) : profile.label, @@ -402,7 +404,7 @@ export class TestingExplorerView extends ViewPane { const menuActions = getFlatContextMenuActions(menu); const postActions: IAction[] = []; - if (profileActions.length > 1) { + if (participatingProfiles > 1) { postActions.push(new Action( 'selectDefaultTestConfigurations', localize('selectDefaultConfigs', 'Select Default Profile'), @@ -423,9 +425,12 @@ export class TestingExplorerView extends ViewPane { } // show menu actions if there are any otherwise don't - return menuActions.length > 0 - ? Separator.join(profileActions, menuActions, postActions) - : Separator.join(profileActions, postActions); + return { + numberOfProfiles: participatingProfiles, + actions: menuActions.length > 0 + ? Separator.join(profileActions, menuActions, postActions) + : Separator.join(profileActions, postActions), + }; } /** @@ -438,7 +443,7 @@ export class TestingExplorerView extends ViewPane { private getRunGroupDropdown(group: TestRunProfileBitset, defaultAction: IAction, options: IActionViewItemOptions) { const dropdownActions = this.getTestConfigGroupActions(group); - if (dropdownActions.length < 2) { + if (dropdownActions.numberOfProfiles < 2) { return super.getActionViewItem(defaultAction, options); } @@ -452,7 +457,7 @@ export class TestingExplorerView extends ViewPane { return this.instantiationService.createInstance( DropdownWithPrimaryActionViewItem, - primaryAction, this.getDropdownAction(), dropdownActions, + primaryAction, this.getDropdownAction(), dropdownActions.actions, '', options ); From 9fc5861de2162b2f6ab6f316c655679fdaf61eb0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:51:32 -0800 Subject: [PATCH 098/200] Speculative fix when pwsh is 'powershell' on mac/linux Fixes #219583 --- src/vs/platform/shell/node/shellEnv.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/shell/node/shellEnv.ts b/src/vs/platform/shell/node/shellEnv.ts index 2d3ef3ead0d49..31a1ebe1ecf11 100644 --- a/src/vs/platform/shell/node/shellEnv.ts +++ b/src/vs/platform/shell/node/shellEnv.ts @@ -129,7 +129,7 @@ async function doResolveUnixShellEnv(logService: ILogService, token: Cancellatio const name = basename(systemShellUnix); let command: string, shellArgs: Array; const extraArgs = ''; - if (/^pwsh(-preview)?$/.test(name)) { + if (/^(?:pwsh(?:-preview)|powershell)$/.test(name)) { // Older versions of PowerShell removes double quotes sometimes so we use "double single quotes" which is how // you escape single quotes inside of a single quoted string. command = `& '${process.execPath}' ${extraArgs} -p '''${mark}'' + JSON.stringify(process.env) + ''${mark}'''`; From c88542ef6216f589c6675b2aac96447b284708ef Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 18 Dec 2024 21:21:30 +0100 Subject: [PATCH 099/200] Fix navigation to single match in tree find (#236515) fixes #236478 --- src/vs/base/browser/ui/tree/abstractTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 38570ae882de3..b8144e58c23f3 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1177,7 +1177,7 @@ export class FindController extends AbstractFindController this.shouldAllowFocus(node)); + this.tree.focusNext(0, true, undefined, (node) => !FuzzyScore.isDefault(node.filterData as any as FuzzyScore)); } const focus = this.tree.getFocus(); From 1147139fbb8174119528d71a99cb9a0688170999 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 18 Dec 2024 12:27:49 -0800 Subject: [PATCH 100/200] fix: copy from context menu for chat references does not work (#236518) --- .../chatReferencesContentPart.ts | 30 +++++++++++++++++-- .../files/browser/fileActions.contribution.ts | 2 +- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts index 3b71a48f0cc90..a06c21d966b0f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts @@ -19,7 +19,8 @@ import { localize, localize2 } from '../../../../../nls.js'; import { getFlatContextMenuActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; import { Action2, IMenuService, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; -import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.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 { FileKind } from '../../../../../platform/files/common/files.js'; import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; @@ -476,7 +477,7 @@ registerAction2(class AddToChatAction extends Action2 { id: MenuId.ChatAttachmentsContext, group: 'chat', order: 1, - when: ExplorerFolderContext.negate(), + when: ContextKeyExpr.and(ResourceContextKey.IsFileSystemResource, ExplorerFolderContext.negate()), }] }); } @@ -498,4 +499,29 @@ registerAction2(class AddToChatAction extends Action2 { } }); +registerAction2(class OpenChatReferenceLinkAction extends Action2 { + + static readonly id = 'workbench.action.chat.copyLink'; + + constructor() { + super({ + id: OpenChatReferenceLinkAction.id, + title: { + ...localize2('copyLink', "Copy Link"), + }, + f1: false, + menu: [{ + id: MenuId.ChatAttachmentsContext, + group: 'chat', + order: 0, + when: ContextKeyExpr.or(ResourceContextKey.Scheme.isEqualTo(Schemas.http), ResourceContextKey.Scheme.isEqualTo(Schemas.https)), + }] + }); + } + + override async run(accessor: ServicesAccessor, resource: URI): Promise { + await accessor.get(IClipboardService).writeResources([resource]); + } +}); + //#endregion diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 9da55d34ce374..be7dddf26ff91 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -770,7 +770,7 @@ MenuRegistry.appendMenuItem(MenuId.ChatAttachmentsContext, { group: 'navigation', order: 10, command: openToSideCommand, - when: ContextKeyExpr.and(ResourceContextKey.HasResource, ExplorerFolderContext.toNegated()) + when: ContextKeyExpr.and(ResourceContextKey.IsFileSystemResource, ExplorerFolderContext.toNegated()) }); MenuRegistry.appendMenuItem(MenuId.ChatAttachmentsContext, { From e6e5856995525c808ad52547beb8fe19802207ea Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 18 Dec 2024 12:39:57 -0800 Subject: [PATCH 101/200] debug: fix unexpected behaviors with duplicate `name`s in `launch.json` (#236513) Suffix duplicated launch configs with a config at the time they're read. In debug we assume the names are unique, so this should fix #231377 and probably other hidden issues as well. --- .../browser/debugConfigurationManager.ts | 61 ++++++++++++------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 0cc18f401f1a3..89edaf1e668fc 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -10,7 +10,6 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import * as json from '../../../../base/common/json.js'; import { IJSONSchema } from '../../../../base/common/jsonSchema.js'; import { DisposableStore, IDisposable, dispose } from '../../../../base/common/lifecycle.js'; -import * as objects from '../../../../base/common/objects.js'; import * as resources from '../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI as uri } from '../../../../base/common/uri.js'; @@ -27,16 +26,16 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkbenchState } from '../../../../platform/workspace/common/workspace.js'; import { IEditorPane } from '../../../common/editor.js'; -import { debugConfigure } from './debugIcons.js'; -import { CONTEXT_DEBUG_CONFIGURATION_TYPE, DebugConfigurationProviderTriggerKind, IAdapterManager, ICompound, IConfig, IConfigPresentation, IConfigurationManager, IDebugConfigurationProvider, IGlobalConfig, IGuessedDebugger, ILaunch } from '../common/debug.js'; -import { launchSchema } from '../common/debugSchemas.js'; -import { getVisibleAndSorted } from '../common/debugUtils.js'; import { launchSchemaId } from '../../../services/configuration/common/configuration.js'; import { ACTIVE_GROUP, IEditorService } from '../../../services/editor/common/editorService.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { IHistoryService } from '../../../services/history/common/history.js'; import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; import { ITextFileService } from '../../../services/textfile/common/textfiles.js'; +import { CONTEXT_DEBUG_CONFIGURATION_TYPE, DebugConfigurationProviderTriggerKind, IAdapterManager, ICompound, IConfig, IConfigPresentation, IConfigurationManager, IDebugConfigurationProvider, IGlobalConfig, IGuessedDebugger, ILaunch } from '../common/debug.js'; +import { launchSchema } from '../common/debugSchemas.js'; +import { getVisibleAndSorted } from '../common/debugUtils.js'; +import { debugConfigure } from './debugIcons.js'; const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); jsonRegistry.registerSchema(launchSchemaId, launchSchema); @@ -509,7 +508,7 @@ abstract class AbstractLaunch implements ILaunch { ) { } getCompound(name: string): ICompound | undefined { - const config = this.getConfig(); + const config = this.getDeduplicatedConfig(); if (!config || !config.compounds) { return undefined; } @@ -518,7 +517,7 @@ abstract class AbstractLaunch implements ILaunch { } getConfigurationNames(ignoreCompoundsAndPresentation = false): string[] { - const config = this.getConfig(); + const config = this.getDeduplicatedConfig(); if (!config || (!Array.isArray(config.configurations) && !Array.isArray(config.compounds))) { return []; } else { @@ -540,21 +539,22 @@ abstract class AbstractLaunch implements ILaunch { getConfiguration(name: string): IConfig | undefined { // We need to clone the configuration in order to be able to make changes to it #42198 - const config = objects.deepClone(this.getConfig()); + const config = this.getDeduplicatedConfig(); if (!config || !config.configurations) { return undefined; } const configuration = config.configurations.find(config => config && config.name === name); - if (configuration) { - if (this instanceof UserLaunch) { - configuration.__configurationTarget = ConfigurationTarget.USER; - } else if (this instanceof WorkspaceLaunch) { - configuration.__configurationTarget = ConfigurationTarget.WORKSPACE; - } else { - configuration.__configurationTarget = ConfigurationTarget.WORKSPACE_FOLDER; - } + if (!configuration) { + return; + } + + if (this instanceof UserLaunch) { + return { ...configuration, __configurationTarget: ConfigurationTarget.USER }; + } else if (this instanceof WorkspaceLaunch) { + return { ...configuration, __configurationTarget: ConfigurationTarget.WORKSPACE }; + } else { + return { ...configuration, __configurationTarget: ConfigurationTarget.WORKSPACE_FOLDER }; } - return configuration; } async getInitialConfigurationContent(folderUri?: uri, type?: string, useInitialConfigs?: boolean, token?: CancellationToken): Promise { @@ -575,9 +575,28 @@ abstract class AbstractLaunch implements ILaunch { return content; } + get hidden(): boolean { return false; } + + private getDeduplicatedConfig(): IGlobalConfig | undefined { + const original = this.getConfig(); + return original && { + version: original.version, + compounds: original.compounds && distinguishConfigsByName(original.compounds), + configurations: original.configurations && distinguishConfigsByName(original.configurations), + }; + } +} + +function distinguishConfigsByName(things: readonly T[]): T[] { + const seen = new Map(); + return things.map(thing => { + const no = seen.get(thing.name) || 0; + seen.set(thing.name, no + 1); + return no === 0 ? thing : { ...thing, name: `${thing.name} (${no})` }; + }); } class Launch extends AbstractLaunch implements ILaunch { @@ -655,11 +674,9 @@ class Launch extends AbstractLaunch implements ILaunch { } async writeConfiguration(configuration: IConfig): Promise { - const fullConfig = objects.deepClone(this.getConfig()!); - if (!fullConfig.configurations) { - fullConfig.configurations = []; - } - fullConfig.configurations.push(configuration); + // note: we don't get the deduplicated config since we don't want that to 'leak' into the file + const fullConfig: Partial = this.getConfig() || {}; + fullConfig.configurations = [...fullConfig.configurations || [], configuration]; await this.configurationService.updateValue('launch', fullConfig, { resource: this.workspace.uri }, ConfigurationTarget.WORKSPACE_FOLDER); } } From 3fd6eef7b9683b6581c0111fd0304ca8e1423035 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 18 Dec 2024 13:04:11 -0800 Subject: [PATCH 102/200] fix: workbench.debug.action.focusRepl resolving before focus is given (#236520) Note: I intentionally did not keep this registered under the command palette because there is a similar duplicate command provided automatically from the views service. Fixes #228852 --- .../debug/browser/debug.contribution.ts | 3 +-- .../contrib/debug/browser/debugCommands.ts | 8 ------- .../workbench/contrib/debug/browser/repl.ts | 24 ++++++++++++++++--- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 6033b0cf9b534..fe281b6c1b143 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -31,7 +31,7 @@ import { CallStackEditorContribution } from './callStackEditorContribution.js'; import { CallStackView } from './callStackView.js'; import { ReplAccessibleView } from './replAccessibleView.js'; import { registerColors } from './debugColors.js'; -import { ADD_CONFIGURATION_ID, ADD_TO_WATCH_ID, ADD_TO_WATCH_LABEL, CALLSTACK_BOTTOM_ID, CALLSTACK_BOTTOM_LABEL, CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CALLSTACK_UP_ID, CALLSTACK_UP_LABEL, CONTINUE_ID, CONTINUE_LABEL, COPY_EVALUATE_PATH_ID, COPY_EVALUATE_PATH_LABEL, COPY_STACK_TRACE_ID, COPY_VALUE_ID, COPY_VALUE_LABEL, DEBUG_COMMAND_CATEGORY, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, EDIT_EXPRESSION_COMMAND_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, PAUSE_ID, PAUSE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, REMOVE_EXPRESSION_COMMAND_ID, RESTART_FRAME_ID, RESTART_LABEL, RESTART_SESSION_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, SELECT_DEBUG_SESSION_ID, SELECT_DEBUG_SESSION_LABEL, SET_EXPRESSION_COMMAND_ID, SHOW_LOADED_SCRIPTS_ID, STEP_INTO_ID, STEP_INTO_LABEL, STEP_INTO_TARGET_ID, STEP_INTO_TARGET_LABEL, STEP_OUT_ID, STEP_OUT_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STOP_ID, STOP_LABEL, TERMINATE_THREAD_ID, TOGGLE_INLINE_BREAKPOINT_ID } from './debugCommands.js'; +import { ADD_CONFIGURATION_ID, ADD_TO_WATCH_ID, ADD_TO_WATCH_LABEL, CALLSTACK_BOTTOM_ID, CALLSTACK_BOTTOM_LABEL, CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CALLSTACK_UP_ID, CALLSTACK_UP_LABEL, CONTINUE_ID, CONTINUE_LABEL, COPY_EVALUATE_PATH_ID, COPY_EVALUATE_PATH_LABEL, COPY_STACK_TRACE_ID, COPY_VALUE_ID, COPY_VALUE_LABEL, DEBUG_COMMAND_CATEGORY, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, EDIT_EXPRESSION_COMMAND_ID, JUMP_TO_CURSOR_ID, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, PAUSE_ID, PAUSE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, REMOVE_EXPRESSION_COMMAND_ID, RESTART_FRAME_ID, RESTART_LABEL, RESTART_SESSION_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, SELECT_DEBUG_SESSION_ID, SELECT_DEBUG_SESSION_LABEL, SET_EXPRESSION_COMMAND_ID, SHOW_LOADED_SCRIPTS_ID, STEP_INTO_ID, STEP_INTO_LABEL, STEP_INTO_TARGET_ID, STEP_INTO_TARGET_LABEL, STEP_OUT_ID, STEP_OUT_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STOP_ID, STOP_LABEL, TERMINATE_THREAD_ID, TOGGLE_INLINE_BREAKPOINT_ID } from './debugCommands.js'; import { DebugConsoleQuickAccess } from './debugConsoleQuickAccess.js'; import { RunToCursorAction, SelectionToReplAction, SelectionToWatchExpressionsAction } from './debugEditorActions.js'; import { DebugEditorContribution } from './debugEditorContribution.js'; @@ -133,7 +133,6 @@ registerDebugCommandPaletteItem(DISCONNECT_ID, DISCONNECT_LABEL, CONTEXT_IN_DEBU registerDebugCommandPaletteItem(DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.or(CONTEXT_FOCUSED_SESSION_IS_ATTACH, ContextKeyExpr.and(CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED))); registerDebugCommandPaletteItem(STOP_ID, STOP_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.or(CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED)); registerDebugCommandPaletteItem(CONTINUE_ID, CONTINUE_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugCommandPaletteItem(FOCUS_REPL_ID, nls.localize2({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, "Focus on Debug Console View")); registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, nls.localize2('jumpToCursor', "Jump to Cursor"), CONTEXT_JUMP_TO_CURSOR_SUPPORTED); registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, nls.localize2('SetNextStatement', "Set Next Statement"), CONTEXT_JUMP_TO_CURSOR_SUPPORTED); registerDebugCommandPaletteItem(RunToCursorAction.ID, RunToCursorAction.LABEL, CONTEXT_DEBUGGERS_AVAILABLE); diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index c6d45bda96265..2d3cb0c46c836 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -679,14 +679,6 @@ CommandsRegistry.registerCommand({ } }); -CommandsRegistry.registerCommand({ - id: FOCUS_REPL_ID, - handler: async (accessor) => { - const viewsService = accessor.get(IViewsService); - await viewsService.openView(REPL_VIEW_ID, true); - } -}); - CommandsRegistry.registerCommand({ id: 'debug.startFromConfig', handler: async (accessor, config: IConfig) => { diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index e9ffbe5a57c2b..2950494204586 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -11,7 +11,7 @@ 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 { RunOnceScheduler, timeout } 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'; @@ -71,6 +71,7 @@ import { CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_REPL, CONTEXT_MULTI_SESSION_REPL, import { Variable } from '../common/debugModel.js'; import { ReplEvaluationResult, ReplGroup } from '../common/replModel.js'; import { FocusSessionActionViewItem } from './debugActionViewItems.js'; +import { DEBUG_COMMAND_CATEGORY, FOCUS_REPL_ID } from './debugCommands.js'; import { DebugExpressionRenderer } from './debugExpressionRenderer.js'; import { debugConsoleClearAll, debugConsoleEvaluationPrompt } from './debugIcons.js'; import './media/repl.css'; @@ -554,9 +555,10 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { this.tree?.domFocus(); } - override focus(): void { + override async focus(): Promise { super.focus(); - setTimeout(() => this.replInput.focus(), 0); + await timeout(0); // wait a task for the repl to get attached to the DOM, #83387 + this.replInput.focus(); } override getActionViewItem(action: IAction): IActionViewItem | undefined { @@ -1209,3 +1211,19 @@ registerAction2(class extends Action2 { } } }); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: FOCUS_REPL_ID, + category: DEBUG_COMMAND_CATEGORY, + title: localize2({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, "Focus on Debug Console View"), + }); + } + + override async run(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const repl = await viewsService.openView(REPL_VIEW_ID); + await repl?.focus(); + } +}); From 37c543ba48a33b8113f514951a8b3e39a04fd6bc Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 18 Dec 2024 22:26:30 +0100 Subject: [PATCH 103/200] Implements gutter indicator (off by default). (#236522) * Implements gutter indicator (off by default). * Adds isSingleLine method to monaco Range * update --- src/vs/editor/browser/observableCodeEditor.ts | 16 ++ src/vs/editor/browser/rect.ts | 135 +++++++++++++ src/vs/editor/common/config/editorOptions.ts | 8 + src/vs/editor/common/core/offsetRange.ts | 4 + .../browser/model/inlineCompletionsModel.ts | 2 +- .../view/inlineEdits/gutterIndicatorView.ts | 189 ++++++++++++++++++ .../browser/view/inlineEdits/indicatorView.ts | 4 +- .../browser/view/inlineEdits/utils.ts | 10 +- .../browser/view/inlineEdits/view.ts | 39 ++-- src/vs/monaco.d.ts | 1 + 10 files changed, 388 insertions(+), 20 deletions(-) create mode 100644 src/vs/editor/browser/rect.ts create mode 100644 src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts diff --git a/src/vs/editor/browser/observableCodeEditor.ts b/src/vs/editor/browser/observableCodeEditor.ts index 781a9895212f6..d8607ae319aae 100644 --- a/src/vs/editor/browser/observableCodeEditor.ts +++ b/src/vs/editor/browser/observableCodeEditor.ts @@ -7,6 +7,8 @@ import { equalsIfDefined, itemsEquals } from '../../base/common/equals.js'; import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../base/common/lifecycle.js'; import { IObservable, ITransaction, TransactionImpl, autorun, autorunOpts, derived, derivedOpts, derivedWithSetter, observableFromEvent, observableSignal, observableValue, observableValueOpts } from '../../base/common/observable.js'; import { EditorOption, FindComputedEditorOptionValueById } from '../common/config/editorOptions.js'; +import { LineRange } from '../common/core/lineRange.js'; +import { OffsetRange } from '../common/core/offsetRange.js'; import { Position } from '../common/core/position.js'; import { Selection } from '../common/core/selection.js'; import { ICursorSelectionChangedEvent } from '../common/cursorEvents.js'; @@ -265,6 +267,20 @@ export class ObservableCodeEditor extends Disposable { }); } + public observeLineOffsetRange(lineRange: IObservable, store: DisposableStore): IObservable { + const start = this.observePosition(lineRange.map(r => new Position(r.startLineNumber, 1)), store); + const end = this.observePosition(lineRange.map(r => new Position(r.endLineNumberExclusive + 1, 1)), store); + + return derived(reader => { + start.read(reader); + end.read(reader); + const range = lineRange.read(reader); + const s = this.editor.getTopForLineNumber(range.startLineNumber) - this.scrollTop.read(reader); + const e = range.isEmpty ? s : (this.editor.getBottomForLineNumber(range.endLineNumberExclusive - 1) - this.scrollTop.read(reader)); + return new OffsetRange(s, e); + }); + } + public observePosition(position: IObservable, store: DisposableStore): IObservable { let pos = position.get(); const result = observableValueOpts({ owner: this, debugName: () => `topLeftOfPosition${pos?.toString()}`, equalsFn: equalsIfDefined(Point.equals) }, new Point(0, 0)); diff --git a/src/vs/editor/browser/rect.ts b/src/vs/editor/browser/rect.ts new file mode 100644 index 0000000000000..b990944eb6762 --- /dev/null +++ b/src/vs/editor/browser/rect.ts @@ -0,0 +1,135 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { OffsetRange } from '../common/core/offsetRange.js'; +import { Point } from './point.js'; + +export class Rect { + public static fromPoint(point: Point): Rect { + return new Rect(point.x, point.y, point.x, point.y); + } + + public static fromLeftTopRightBottom(left: number, top: number, right: number, bottom: number): Rect { + return new Rect(left, top, right, bottom); + } + + public static fromLeftTopWidthHeight(left: number, top: number, width: number, height: number): Rect { + return new Rect(left, top, left + width, top + height); + } + + public static fromRanges(leftRight: OffsetRange, topBottom: OffsetRange): Rect { + return new Rect(leftRight.start, topBottom.start, leftRight.endExclusive, topBottom.endExclusive); + } + + public static hull(rects: Rect[]): Rect { + let left = Number.MAX_VALUE; + let top = Number.MAX_VALUE; + let right = Number.MIN_VALUE; + let bottom = Number.MIN_VALUE; + + for (const rect of rects) { + left = Math.min(left, rect.left); + top = Math.min(top, rect.top); + right = Math.max(right, rect.right); + bottom = Math.max(bottom, rect.bottom); + } + + return new Rect(left, top, right, bottom); + } + + public readonly width = this.right - this.left; + public readonly height = this.bottom - this.top; + + constructor( + public readonly left: number, + public readonly top: number, + public readonly right: number, + public readonly bottom: number, + ) { + if (left > right || top > bottom) { + throw new Error('Invalid arguments'); + } + } + + withMargin(margin: number): Rect { + return new Rect(this.left - margin, this.top - margin, this.right + margin, this.bottom + margin); + } + + intersectVertical(range: OffsetRange): Rect { + return new Rect( + this.left, + Math.max(this.top, range.start), + this.right, + Math.min(this.bottom, range.endExclusive), + ); + } + + toString(): string { + return `Rect{(${this.left},${this.top}), (${this.right},${this.bottom})}`; + } + + intersect(parent: Rect): Rect | undefined { + const left = Math.max(this.left, parent.left); + const right = Math.min(this.right, parent.right); + const top = Math.max(this.top, parent.top); + const bottom = Math.min(this.bottom, parent.bottom); + + if (left > right || top > bottom) { + return undefined; + } + + return new Rect(left, top, right, bottom); + } + + union(other: Rect): Rect { + return new Rect( + Math.min(this.left, other.left), + Math.min(this.top, other.top), + Math.max(this.right, other.right), + Math.max(this.bottom, other.bottom), + ); + } + + containsRect(other: Rect): boolean { + return this.left <= other.left + && this.top <= other.top + && this.right >= other.right + && this.bottom >= other.bottom; + } + + moveToBeContainedIn(parent: Rect): Rect { + const width = this.width; + const height = this.height; + + let left = this.left; + let top = this.top; + + if (left < parent.left) { + left = parent.left; + } else if (left + width > parent.right) { + left = parent.right - width; + } + + if (top < parent.top) { + top = parent.top; + } else if (top + height > parent.bottom) { + top = parent.bottom - height; + } + + return new Rect(left, top, left + width, top + height); + } + + withWidth(width: number): Rect { + return new Rect(this.left, this.top, this.left + width, this.bottom); + } + + withHeight(height: number): Rect { + return new Rect(this.left, this.top, this.right, this.top + height); + } + + withTop(top: number): Rect { + return new Rect(this.left, top, this.right, this.bottom); + } +} diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 0c25596358196..c7481985f2f76 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -4198,6 +4198,7 @@ export interface IInlineSuggestOptions { useMixedLinesDiff?: 'never' | 'whenPossible' | 'forStableInsertions' | 'afterJumpWhenPossible'; useInterleavedLinesDiff?: 'never' | 'always' | 'afterJump'; onlyShowWhenCloseToCursor?: boolean; + useGutterIndicator?: boolean; }; }; } @@ -4230,6 +4231,7 @@ class InlineEditorSuggest extends BaseEditorOption { await this._deltaSelectedInlineCompletionIndex(-1); } - public async accept(editor: ICodeEditor): Promise { + public async accept(editor: ICodeEditor = this._editor): Promise { if (editor.getModel() !== this.textModel) { throw new BugIndicatingError(); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts new file mode 100644 index 0000000000000..936ab8b56b3be --- /dev/null +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts @@ -0,0 +1,189 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { renderIcon } from '../../../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { Codicon } from '../../../../../../base/common/codicons.js'; +import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import { IObservable, IReader, constObservable, derived, observableFromEvent } from '../../../../../../base/common/observable.js'; +import { buttonBackground, buttonForeground, buttonSecondaryBackground, buttonSecondaryForeground } from '../../../../../../platform/theme/common/colorRegistry.js'; +import { registerColor, transparent } from '../../../../../../platform/theme/common/colorUtils.js'; +import { ObservableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; +import { Rect } from '../../../../../browser/rect.js'; +import { EditorOption } from '../../../../../common/config/editorOptions.js'; +import { LineRange } from '../../../../../common/core/lineRange.js'; +import { OffsetRange } from '../../../../../common/core/offsetRange.js'; +import { StickyScrollController } from '../../../../stickyScroll/browser/stickyScrollController.js'; +import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js'; +import { mapOutFalsy, n } from './utils.js'; + +export const inlineEditIndicatorPrimaryForeground = registerColor('inlineEdit.gutterIndicator.primaryForeground', buttonForeground, 'Foreground color for the primary inline edit gutter indicator.'); +export const inlineEditIndicatorPrimaryBackground = registerColor('inlineEdit.gutterIndicator.primaryBackground', buttonBackground, 'Background color for the primary inline edit gutter indicator.'); + +export const inlineEditIndicatorSecondaryForeground = registerColor('inlineEdit.gutterIndicator.secondaryForeground', buttonSecondaryForeground, 'Foreground color for the secondary inline edit gutter indicator.'); +export const inlineEditIndicatorSecondaryBackground = registerColor('inlineEdit.gutterIndicator.secondaryBackground', buttonSecondaryBackground, 'Background color for the secondary inline edit gutter indicator.'); + +export const inlineEditIndicatorsuccessfulForeground = registerColor('inlineEdit.gutterIndicator.successfulForeground', buttonForeground, 'Foreground color for the successful inline edit gutter indicator.'); +export const inlineEditIndicatorsuccessfulBackground = registerColor('inlineEdit.gutterIndicator.successfulBackground', { light: '#2e825c', dark: '#2e825c', hcLight: '#2e825c', hcDark: '#2e825c' }, 'Background color for the successful inline edit gutter indicator.'); + +export const inlineEditIndicatorBackground = registerColor( + 'inlineEdit.gutterIndicator.background', + { + hcDark: transparent('tab.inactiveBackground', 0.5), + hcLight: transparent('tab.inactiveBackground', 0.5), + dark: transparent('tab.inactiveBackground', 0.5), + light: '#5f5f5f18', + }, + 'Background color for the inline edit gutter indicator.' +); + + +export class InlineEditsGutterIndicator extends Disposable { + private readonly _state = derived(reader => { + const range = mapOutFalsy(this._originalRange).read(reader); + if (!range) { + return undefined; + } + + return { + range, + lineOffsetRange: this._editorObs.observeLineOffsetRange(range, this._store), + }; + }); + + private _stickyScrollController = StickyScrollController.get(this._editorObs.editor); + private readonly _stickyScrollHeight = this._stickyScrollController ? observableFromEvent(this._stickyScrollController.onDidChangeStickyScrollHeight, () => this._stickyScrollController!.stickyScrollWidgetHeight) : constObservable(0); + + + private readonly _layout = derived(reader => { + const s = this._state.read(reader); + if (!s) { return undefined; } + + const layout = this._editorObs.layoutInfo.read(reader); + + const fullViewPort = Rect.fromLeftTopRightBottom(0, 0, layout.width, layout.height); + const viewPortWithStickyScroll = fullViewPort.withTop(this._stickyScrollHeight.read(reader)); + + const targetVertRange = s.lineOffsetRange.read(reader); + + const space = 1; + + const targetRect = Rect.fromRanges(OffsetRange.fromTo(space, layout.lineNumbersLeft + layout.lineNumbersWidth + 4), targetVertRange); + + + const lineHeight = this._editorObs.getOption(EditorOption.lineHeight).read(reader); + const pillRect = targetRect.withHeight(lineHeight).withWidth(22); + const pillRectMoved = pillRect.moveToBeContainedIn(viewPortWithStickyScroll); + + const rect = targetRect; + + const iconRect = (targetRect.containsRect(pillRectMoved)) + ? pillRectMoved + : pillRectMoved.moveToBeContainedIn(fullViewPort.intersect(targetRect.union(fullViewPort.withHeight(lineHeight)))!); //viewPortWithStickyScroll.intersect(rect)!; + + return { + rect, + iconRect, + mode: (iconRect.top === targetRect.top ? 'right' as const + : iconRect.top > targetRect.top ? 'top' as const : 'bottom' as const), + docked: rect.containsRect(iconRect) && viewPortWithStickyScroll.containsRect(iconRect), + }; + }); + + private readonly _mode = derived(this, reader => { + const m = this._model.read(reader); + if (m && m.tabShouldAcceptInlineEdit.read(reader)) { return 'accept' as const; } + if (m && m.tabShouldJumpToInlineEdit.read(reader)) { return 'jump' as const; } + return 'inactive' as const; + }); + + private readonly _onClickAction = derived(this, reader => { + if (this._layout.map(d => d && d.docked).read(reader)) { + return { + label: 'Click to accept inline edit', + action: () => { this._model.get()?.accept(); } + }; + } else { + return { + label: 'Click to jump to inline edit', + action: () => { this._model.get()?.jump(); } + }; + } + }); + + private readonly _indicator = n.div({ + class: 'inline-edits-view-gutter-indicator', + onclick: () => this._onClickAction.get().action(), + title: this._onClickAction.map(a => a.label), + style: { + position: 'absolute', + overflow: 'visible', + }, + }, mapOutFalsy(this._layout).map(l => !l ? [] : [ + n.div({ + style: { + position: 'absolute', + background: 'var(--vscode-inlineEdit-gutterIndicator-background)', + borderRadius: '4px', + ...rectToProps(reader => l.read(reader).rect), + } + }), + n.div({ + class: 'icon', + style: { + cursor: 'pointer', + zIndex: '1000', + position: 'absolute', + backgroundColor: this._mode.map(v => ({ + inactive: 'var(--vscode-inlineEdit-gutterIndicator-secondaryBackground)', + jump: 'var(--vscode-inlineEdit-gutterIndicator-primaryBackground)', + accept: 'var(--vscode-inlineEdit-gutterIndicator-successfulBackground)', + }[v])), + '--vscodeIconForeground': this._mode.map(v => ({ + inactive: 'var(--vscode-inlineEdit-gutterIndicator-secondaryForeground)', + jump: 'var(--vscode-inlineEdit-gutterIndicator-primaryForeground)', + accept: 'var(--vscode-inlineEdit-gutterIndicator-successfulForeground)', + }[v])), + borderRadius: '4px', + display: 'flex', + justifyContent: 'center', + transition: 'background-color 0.2s ease-in-out', + ...rectToProps(reader => l.read(reader).iconRect), + } + }, [ + n.div({ + style: { + rotate: l.map(l => ({ right: '0deg', bottom: '90deg', top: '-90deg' }[l.mode])), + transition: 'rotate 0.2s ease-in-out', + } + }, [ + renderIcon(Codicon.arrowRight), + ]) + ]), + ])).keepUpdated(this._store); + + constructor( + private readonly _editorObs: ObservableCodeEditor, + private readonly _originalRange: IObservable, + private readonly _model: IObservable, + ) { + super(); + + this._register(this._editorObs.createOverlayWidget({ + domNode: this._indicator.element, + position: constObservable(null), + allowEditorOverflow: false, + minContentWidthInPx: constObservable(0), + })); + } +} + +function rectToProps(fn: (reader: IReader) => Rect): any { + return { + left: derived(reader => fn(reader).left), + top: derived(reader => fn(reader).top), + width: derived(reader => fn(reader).right - fn(reader).left), + height: derived(reader => fn(reader).bottom - fn(reader).top), + }; +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/indicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/indicatorView.ts index 052451f326dc9..f584566365836 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/indicatorView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/indicatorView.ts @@ -19,11 +19,9 @@ export interface IInlineEditsIndicatorState { showAlways: boolean; } -// editorHoverForeground + export const inlineEditIndicatorForeground = registerColor('inlineEdit.indicator.foreground', buttonForeground, ''); -// editorHoverBackground export const inlineEditIndicatorBackground = registerColor('inlineEdit.indicator.background', buttonBackground, ''); -// editorHoverBorder export const inlineEditIndicatorBorder = registerColor('inlineEdit.indicator.border', buttonSeparator, ''); export class InlineEditsIndicator extends Disposable { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts index d9928a05b2eb0..73b9cbb561865 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts @@ -9,7 +9,7 @@ import { numberComparator } from '../../../../../../base/common/arrays.js'; import { findFirstMin } from '../../../../../../base/common/arraysFind.js'; import { BugIndicatingError } from '../../../../../../base/common/errors.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../../../base/common/lifecycle.js'; -import { derived, IObservable, IReader, observableValue, transaction } from '../../../../../../base/common/observable.js'; +import { derived, derivedObservableWithCache, IObservable, IReader, observableValue, transaction } from '../../../../../../base/common/observable.js'; import { OS } from '../../../../../../base/common/platform.js'; import { getIndentationLength, splitLines } from '../../../../../../base/common/strings.js'; import { URI } from '../../../../../../base/common/uri.js'; @@ -329,6 +329,8 @@ export abstract class ObserverNode extends Disposab } else { this._element.tabIndex = value; } + } else if (key.startsWith('on')) { + (this._element as any)[key] = value; } else { if (isObservable(value)) { this._deriveds.push(derived(this, reader => { @@ -425,12 +427,16 @@ type ElementAttributeKeys = Partial<{ }>; export function mapOutFalsy(obs: IObservable): IObservable | undefined | null | false> { + const nonUndefinedObs = derivedObservableWithCache(undefined, (reader, lastValue) => obs.read(reader) || lastValue); + return derived(reader => { + nonUndefinedObs.read(reader); const val = obs.read(reader); if (!val) { return undefined; } - return obs as IObservable; + + return nonUndefinedObs as IObservable; }); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts index 945c91b9e130a..68bc963e374a7 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import { derived, IObservable, IReader } from '../../../../../../base/common/observable.js'; +import { autorunWithStore, derived, IObservable, IReader } from '../../../../../../base/common/observable.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { ICodeEditor } from '../../../../../browser/editorBrowser.js'; import { observableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; @@ -15,6 +15,7 @@ import { StringText } from '../../../../../common/core/textEdit.js'; import { DetailedLineRangeMapping, lineRangeMappingFromRangeMappings, RangeMapping } from '../../../../../common/diff/rangeMapping.js'; import { TextModel } from '../../../../../common/model/textModel.js'; import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js'; +import { InlineEditsGutterIndicator } from './gutterIndicatorView.js'; import { IInlineEditsIndicatorState, InlineEditsIndicator } from './indicatorView.js'; import { IOriginalEditorInlineDiffViewState, OriginalEditorInlineDiffView } from './inlineDiffView.js'; import { InlineEditsSideBySideDiff } from './sideBySideDiff.js'; @@ -118,19 +119,29 @@ export class InlineEditsView extends Disposable { protected readonly _inlineDiffView = this._register(new OriginalEditorInlineDiffView(this._editor, this._inlineDiffViewState, this._previewTextModel)); - protected readonly _indicator = this._register(new InlineEditsIndicator( - this._editorObs, - derived(reader => { - const state = this._uiState.read(reader); - if (!state) { return undefined; } - - const range = state.originalDisplayRange; - const top = this._editor.getTopForLineNumber(range.startLineNumber) - this._editorObs.scrollTop.read(reader); - - return { editTop: top, showAlways: state.state !== 'sideBySide' }; - }), - this._model, - )); + private readonly _useGutterIndicator = observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.experimental.useGutterIndicator); + + protected readonly _indicator = this._register(autorunWithStore((reader, store) => { + if (this._useGutterIndicator.read(reader)) { + store.add(new InlineEditsGutterIndicator( + this._editorObs, + this._uiState.map(s => s && s.originalDisplayRange), + this._model, + )); + } else { + store.add(new InlineEditsIndicator( + this._editorObs, + derived(reader => { + const state = this._uiState.read(reader); + if (!state) { return undefined; } + const range = state.originalDisplayRange; + const top = this._editor.getTopForLineNumber(range.startLineNumber) - this._editorObs.scrollTop.read(reader); + return { editTop: top, showAlways: state.state !== 'sideBySide' }; + }), + this._model, + )); + } + })); private determinRenderState(edit: InlineEditWithChanges, reader: IReader, diff: DetailedLineRangeMapping[]) { if (edit.isCollapsed) { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 77cd7ae49a531..7b6d78e85528c 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4605,6 +4605,7 @@ declare namespace monaco.editor { useMixedLinesDiff?: 'never' | 'whenPossible' | 'forStableInsertions' | 'afterJumpWhenPossible'; useInterleavedLinesDiff?: 'never' | 'always' | 'afterJump'; onlyShowWhenCloseToCursor?: boolean; + useGutterIndicator?: boolean; }; }; } From 0900a621135265c95c0a499b5123e2cae848ae1f Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 18 Dec 2024 15:40:29 -0600 Subject: [PATCH 104/200] get terminal completions to work for screen reader users (#236516) fix #235022 --- .../suggest/browser/simpleSuggestWidget.ts | 72 ++++++++++++++++++- .../browser/simpleSuggestWidgetRenderer.ts | 2 +- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts b/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts index 61fb7437cea94..bc9c9ea0cba97 100644 --- a/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts +++ b/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts @@ -10,9 +10,9 @@ import { List } from '../../../../base/browser/ui/list/listWidget.js'; import { ResizableHTMLElement } from '../../../../base/browser/ui/resizable/resizable.js'; import { SimpleCompletionItem } from './simpleCompletionItem.js'; import { LineContext, SimpleCompletionModel } from './simpleCompletionModel.js'; -import { SimpleSuggestWidgetItemRenderer, type ISimpleSuggestWidgetFontInfo } from './simpleSuggestWidgetRenderer.js'; +import { getAriaId, SimpleSuggestWidgetItemRenderer, type ISimpleSuggestWidgetFontInfo } from './simpleSuggestWidgetRenderer.js'; import { TimeoutTimer } from '../../../../base/common/async.js'; -import { Emitter, Event } from '../../../../base/common/event.js'; +import { Emitter, Event, PauseableEmitter } from '../../../../base/common/event.js'; import { MutableDisposable, Disposable } from '../../../../base/common/lifecycle.js'; import { clamp } from '../../../../base/common/numbers.js'; import { localize } from '../../../../nls.js'; @@ -68,7 +68,9 @@ export class SimpleSuggestWidget extends Disposable { private _forceRenderingAbove: boolean = false; private _preference?: WidgetPositionPreference; private readonly _pendingLayout = this._register(new MutableDisposable()); - + // private _currentSuggestionDetails?: CancelablePromise; + private _focusedItem?: SimpleCompletionItem; + private _ignoreFocusEvents: boolean = false; readonly element: ResizableHTMLElement; private readonly _messageElement: HTMLElement; private readonly _listElement: HTMLElement; @@ -83,6 +85,8 @@ export class SimpleSuggestWidget extends Disposable { readonly onDidHide: Event = this._onDidHide.event; private readonly _onDidShow = this._register(new Emitter()); readonly onDidShow: Event = this._onDidShow.event; + private readonly _onDidFocus = new PauseableEmitter(); + readonly onDidFocus: Event = this._onDidFocus.event; get list(): List { return this._list; } @@ -206,6 +210,7 @@ export class SimpleSuggestWidget extends Disposable { this._register(this._list.onMouseDown(e => this._onListMouseDownOrTap(e))); this._register(this._list.onTap(e => this._onListMouseDownOrTap(e))); + this._register(this._list.onDidChangeFocus(e => this._onListFocus(e))); this._register(this._list.onDidChangeSelection(e => this._onListSelection(e))); this._register(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('editor.suggest.showIcons')) { @@ -214,6 +219,67 @@ export class SimpleSuggestWidget extends Disposable { })); } + private _onListFocus(e: IListEvent): void { + if (this._ignoreFocusEvents) { + return; + } + + if (this._state === State.Details) { + // This can happen when focus is in the details-panel and when + // arrow keys are pressed to select next/prev items + this._setState(State.Open); + } + + if (!e.elements.length) { + // if (this._currentSuggestionDetails) { + // this._currentSuggestionDetails.cancel(); + // this._currentSuggestionDetails = undefined; + // this._focusedItem = undefined; + // } + this._clearAriaActiveDescendant(); + return; + } + + if (!this._completionModel) { + return; + } + + // this._ctxSuggestWidgetHasFocusedSuggestion.set(true); + const item = e.elements[0]; + const index = e.indexes[0]; + + if (item !== this._focusedItem) { + + // this._currentSuggestionDetails?.cancel(); + // this._currentSuggestionDetails = undefined; + + this._focusedItem = item; + + this._list.reveal(index); + const id = getAriaId(index); + const node = dom.getActiveWindow().document.activeElement; + if (node && id) { + node.setAttribute('aria-haspopup', 'true'); + node.setAttribute('aria-autocomplete', 'list'); + node.setAttribute('aria-activedescendant', id); + } else { + this._clearAriaActiveDescendant(); + } + } + // emit an event + this._onDidFocus.fire({ item, index, model: this._completionModel }); + } + + private _clearAriaActiveDescendant(): void { + const node = dom.getActiveWindow().document.activeElement; + if (!node) { + return; + } + node.setAttribute('aria-haspopup', 'false'); + node.setAttribute('aria-autocomplete', 'both'); + node.removeAttribute('aria-activedescendant'); + } + private _cursorPosition?: { top: number; left: number; height: number }; setCompletionModel(completionModel: SimpleCompletionModel) { diff --git a/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetRenderer.ts b/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetRenderer.ts index 86e0f510b0f3d..83605e2156de9 100644 --- a/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetRenderer.ts +++ b/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetRenderer.ts @@ -14,7 +14,7 @@ import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; export function getAriaId(index: number): string { - return `simple-suggest-aria-id:${index}`; + return `simple-suggest-aria-id-${index}`; } export interface ISimpleSuggestionTemplateData { From 4572abc566c41ae926fcd01c979f9e312c69a7ca Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 18 Dec 2024 13:51:49 -0800 Subject: [PATCH 105/200] debug: fix REPL input not resizing when pasting content that wraps (#236519) Fixes #229541 --- src/vs/workbench/contrib/debug/browser/repl.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 2950494204586..4bcfe48be3e07 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -109,7 +109,6 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { private replInput!: CodeEditorWidget; private replInputContainer!: HTMLElement; private bodyContentDimension: dom.Dimension | undefined; - private replInputLineCount = 1; private model: ITextModel | undefined; private setHistoryNavigationEnablement!: (enabled: boolean) => void; private scopedInstantiationService!: IInstantiationService; @@ -476,9 +475,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { revealLastElement(this.tree!); this.history.add(this.replInput.getValue()); this.replInput.setValue(''); - const shouldRelayout = this.replInputLineCount > 1; - this.replInputLineCount = 1; - if (shouldRelayout && this.bodyContentDimension) { + if (this.bodyContentDimension) { // Trigger a layout to shrink a potential multi line input this.layoutBodyContent(this.bodyContentDimension.height, this.bodyContentDimension.width); } @@ -737,12 +734,14 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { this.replInput = this.scopedInstantiationService.createInstance(CodeEditorWidget, this.replInputContainer, options, getSimpleCodeEditorWidgetOptions()); + let lastContentHeight = -1; 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; + + const contentHeight = this.replInput.getContentHeight(); + if (contentHeight !== lastContentHeight) { + lastContentHeight = contentHeight; if (this.bodyContentDimension) { this.layoutBodyContent(this.bodyContentDimension.height, this.bodyContentDimension.width); } From a7d5ca7e7d5d97e6cefa9fc9f4d16950829bdfd7 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Wed, 18 Dec 2024 14:12:51 -0800 Subject: [PATCH 106/200] Fix logic for notebook outline data source isEmpty() fn (#236525) amend logic for notebook outline data source isEmpty() --- .../contrib/outline/notebookOutline.ts | 61 ++++++++++++------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index 7df78960c43d4..0e3812f9f8be6 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -346,6 +346,28 @@ export class NotebookQuickPickProvider implements IQuickPickDataSource NotebookOutlineConstants.NonHeaderOutlineLevel) // show symbols off + cell is code + is level >7 (nb symbol levels) + ) { + return true; + } + + return false; +} + export class NotebookOutlinePaneProvider implements IDataSource { private readonly _disposables = new DisposableStore(); @@ -381,14 +403,14 @@ export class NotebookOutlinePaneProvider implements IDataSource NotebookOutlineConstants.NonHeaderOutlineLevel) // show symbols off + cell is code + is level >7 (nb symbol levels) - ) { - return true; - } - - return false; - } - *getChildren(element: NotebookCellOutline | OutlineEntry): Iterable { const isOutline = element instanceof NotebookCellOutline; const entries = isOutline ? this.outlineDataSourceRef?.object?.entries ?? [] : element.children; @@ -518,7 +521,9 @@ export class NotebookCellOutline implements IOutline { private _breadcrumbsDataSource!: IBreadcrumbsDataSource; // view settings + private outlineShowCodeCells: boolean; private outlineShowCodeCellSymbols: boolean; + private outlineShowMarkdownHeadersOnly: boolean; // getters get activeElement(): OutlineEntry | undefined { @@ -538,7 +543,13 @@ export class NotebookCellOutline implements IOutline { return this._outlineDataSourceReference?.object?.uri; } get isEmpty(): boolean { - return this._outlineDataSourceReference?.object?.isEmpty ?? true; + if (!this._outlineDataSourceReference?.object?.entries) { + return true; + } + + return !this._outlineDataSourceReference.object.entries.some(entry => { + return !filterEntry(entry, this.outlineShowMarkdownHeadersOnly, this.outlineShowCodeCells, this.outlineShowCodeCellSymbols); + }); } private checkDelayer() { @@ -558,7 +569,9 @@ export class NotebookCellOutline implements IOutline { @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, ) { + this.outlineShowCodeCells = this._configurationService.getValue(NotebookSetting.outlineShowCodeCells); this.outlineShowCodeCellSymbols = this._configurationService.getValue(NotebookSetting.outlineShowCodeCellSymbols); + this.outlineShowMarkdownHeadersOnly = this._configurationService.getValue(NotebookSetting.outlineShowMarkdownHeadersOnly); this.initializeOutline(); @@ -615,6 +628,10 @@ export class NotebookCellOutline implements IOutline { e.affectsConfiguration(NotebookSetting.outlineShowCodeCellSymbols) || e.affectsConfiguration(NotebookSetting.breadcrumbsShowCodeCells) ) { + this.outlineShowCodeCells = this._configurationService.getValue(NotebookSetting.outlineShowCodeCells); + this.outlineShowCodeCellSymbols = this._configurationService.getValue(NotebookSetting.outlineShowCodeCellSymbols); + this.outlineShowMarkdownHeadersOnly = this._configurationService.getValue(NotebookSetting.outlineShowMarkdownHeadersOnly); + this.delayedRecomputeState(); } })); From 6869b35a03738298550ab62968cc817ab15828e3 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 18 Dec 2024 14:16:19 -0800 Subject: [PATCH 107/200] Show chat participants and tools on extension features page (#236526) * Show chat participants and tools on extension features page * Remove "ID" --- .../platform/extensions/common/extensions.ts | 19 ++++++- .../browser/chatParticipant.contribution.ts | 54 +++++++++++++++++- .../common/chatParticipantContribTypes.ts | 1 - .../tools/languageModelToolsContribution.ts | 55 ++++++++++++++++++- 4 files changed, 122 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 687fd50dc360b..d6758a9b54dfe 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -167,6 +167,22 @@ export interface ILocalizationContribution { minimalTranslations?: { [key: string]: string }; } +export interface IChatParticipantContribution { + id: string; + name: string; + fullName: string; + description?: string; + isDefault?: boolean; + commands?: { name: string }[]; +} + +export interface IToolContribution { + name: string; + displayName: string; + modelDescription: string; + userDescription?: string; +} + export interface IExtensionContributions { commands?: ICommand[]; configuration?: any; @@ -192,7 +208,8 @@ export interface IExtensionContributions { readonly notebooks?: INotebookEntry[]; readonly notebookRenderer?: INotebookRendererContribution[]; readonly debugVisualizers?: IDebugVisualizationContribution[]; - readonly chatParticipants?: ReadonlyArray<{ id: string }>; + readonly chatParticipants?: ReadonlyArray; + readonly languageModelTools?: ReadonlyArray; } export interface IExtensionCapabilities { diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts index 6326c643d9c73..3d16ee993b751 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts @@ -7,12 +7,13 @@ 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 { MarkdownString } from '../../../../base/common/htmlContent.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 { ExtensionIdentifier, IExtensionManifest } 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'; @@ -20,6 +21,7 @@ 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 { IExtensionFeatureTableRenderer, IRenderedData, ITableData, IRowData, IExtensionFeaturesRegistry, Extensions } from '../../../services/extensionManagement/common/extensionFeatures.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'; @@ -202,7 +204,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { continue; } - if ((providerDescriptor.defaultImplicitVariables || providerDescriptor.locations) && !isProposedApiEnabled(extension.description, 'chatParticipantAdditions')) { + if (providerDescriptor.locations && !isProposedApiEnabled(extension.description, 'chatParticipantAdditions')) { this.logService.error(`Extension '${extension.description.identifier.value}' CANNOT use API proposal: chatParticipantAdditions.`); continue; } @@ -423,3 +425,51 @@ export class ChatCompatibilityNotifier extends Disposable implements IWorkbenchC })); } } + +class ChatParticipantDataRenderer extends Disposable implements IExtensionFeatureTableRenderer { + readonly type = 'table'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.chatParticipants; + } + + render(manifest: IExtensionManifest): IRenderedData { + const nonDefaultContributions = manifest.contributes?.chatParticipants?.filter(c => !c.isDefault) ?? []; + if (!nonDefaultContributions.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const headers = [ + localize('participantName', "Name"), + localize('participantFullName', "Full Name"), + localize('participantDescription', "Description"), + localize('participantCommands', "Commands"), + ]; + + const rows: IRowData[][] = nonDefaultContributions.map(d => { + return [ + '@' + d.name, + d.fullName, + d.description ?? '-', + d.commands?.length ? new MarkdownString(d.commands.map(c => `- /` + c.name).join('\n')) : '-' + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'chatParticipants', + label: localize('chatParticipants', "Chat Participants"), + access: { + canToggle: false + }, + renderer: new SyncDescriptor(ChatParticipantDataRenderer), +}); diff --git a/src/vs/workbench/contrib/chat/common/chatParticipantContribTypes.ts b/src/vs/workbench/contrib/chat/common/chatParticipantContribTypes.ts index e17e9a1322f1d..211d7e7e9c9f7 100644 --- a/src/vs/workbench/contrib/chat/common/chatParticipantContribTypes.ts +++ b/src/vs/workbench/contrib/chat/common/chatParticipantContribTypes.ts @@ -25,7 +25,6 @@ export interface IRawChatParticipantContribution { isSticky?: boolean; sampleRequest?: string; commands?: IRawChatCommandContribution[]; - defaultImplicitVariables?: string[]; locations?: RawChatParticipantLocation[]; disambiguation?: { category: string; categoryName?: string /** Deprecated */; description: string; examples: string[] }[]; } diff --git a/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts b/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts index de36f417e6a4f..e78ef045c614f 100644 --- a/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts +++ b/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts @@ -3,19 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { IJSONSchema } from '../../../../../base/common/jsonSchema.js'; -import { DisposableMap } from '../../../../../base/common/lifecycle.js'; +import { DisposableMap, Disposable } from '../../../../../base/common/lifecycle.js'; import { joinPath } from '../../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { localize } from '../../../../../nls.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; -import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js'; +import { ExtensionIdentifier, IExtensionManifest } from '../../../../../platform/extensions/common/extensions.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; import { IWorkbenchContribution } from '../../../../common/contributions.js'; import { ILanguageModelToolsService, IToolData } from '../languageModelToolsService.js'; import * as extensionsRegistry from '../../../../services/extensions/common/extensionsRegistry.js'; import { toolsParametersSchemaSchemaId } from './languageModelToolsParametersSchema.js'; +import { MarkdownString } from '../../../../../base/common/htmlContent.js'; +import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js'; +import { Registry } from '../../../../../platform/registry/common/platform.js'; +import { IExtensionFeatureTableRenderer, IRenderedData, ITableData, IRowData, IExtensionFeaturesRegistry, Extensions } from '../../../../services/extensionManagement/common/extensionFeatures.js'; export interface IRawToolContribution { name: string; @@ -192,3 +195,49 @@ export class LanguageModelToolsExtensionPointHandler implements IWorkbenchContri }); } } + +class LanguageModelToolDataRenderer extends Disposable implements IExtensionFeatureTableRenderer { + readonly type = 'table'; + + shouldRender(manifest: IExtensionManifest): boolean { + return !!manifest.contributes?.languageModelTools; + } + + render(manifest: IExtensionManifest): IRenderedData { + const contribs = manifest.contributes?.languageModelTools ?? []; + if (!contribs.length) { + return { data: { headers: [], rows: [] }, dispose: () => { } }; + } + + const headers = [ + localize('toolTableName', "Name"), + localize('toolTableDisplayName', "Display Name"), + localize('toolTableDescription', "Description"), + ]; + + const rows: IRowData[][] = contribs.map(t => { + return [ + new MarkdownString(`\`${t.name}\``), + t.displayName, + t.userDescription ?? t.modelDescription, + ]; + }); + + return { + data: { + headers, + rows + }, + dispose: () => { } + }; + } +} + +Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: 'languageModelTools', + label: localize('langModelTools', "Language Model Tools"), + access: { + canToggle: false + }, + renderer: new SyncDescriptor(LanguageModelToolDataRenderer), +}); From 37b1016c80c5e6cd667c382765651055ad9f0ba8 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 18 Dec 2024 15:04:42 -0800 Subject: [PATCH 108/200] Lock some SCM strings (#236531) Fixes https://github.com/microsoft/vscode/issues/236530 --- extensions/git/package.nls.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index d706b650c8a0e..6db253137e15a 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -310,6 +310,8 @@ "message": "[Download Git for Windows](https://git-scm.com/download/win)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", "comment": [ "{Locked='](command:workbench.action.reloadWindow'}", + "{Locked='](command:git.showOutput'}", + "{Locked='](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22'}", "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] @@ -318,6 +320,8 @@ "message": "[Download Git for macOS](https://git-scm.com/download/mac)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", "comment": [ "{Locked='](command:workbench.action.reloadWindow'}", + "{Locked='](command:git.showOutput'}", + "{Locked='](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22'}", "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] @@ -326,11 +330,19 @@ "message": "Source control depends on Git being installed.\n[Download Git for Linux](https://git-scm.com/download/linux)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", "comment": [ "{Locked='](command:workbench.action.reloadWindow'}", + "{Locked='](command:git.showOutput'}", + "{Locked='](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22'}", "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, - "view.workbench.scm.missing": "Install Git, a popular source control system, to track code changes and collaborate with others. Learn more in our [Git guides](https://aka.ms/vscode-scm).", + "view.workbench.scm.missing": { + "message": "Install Git, a popular source control system, to track code changes and collaborate with others. Learn more in our [Git guides](https://aka.ms/vscode-scm).", + "comment": [ + "{Locked='](https://aka.ms/vscode-scm'}", + "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" + ] + }, "view.workbench.scm.disabled": { "message": "If you would like to use Git features, please enable Git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "comment": [ From 03e9e31965788888ef75b7b5b5baa73897f2db0a Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 18 Dec 2024 17:25:00 -0800 Subject: [PATCH 109/200] Await remote extensions before checking enablement (#236538) await remote extensions before checking enablement --- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index aab4103ddac9c..f42fd26ada529 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -60,6 +60,7 @@ import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { URI } from '../../../../base/common/uri.js'; import { IHostService } from '../../../services/host/browser/host.js'; import Severity from '../../../../base/common/severity.js'; +import { IRemoteExtensionsScannerService } from '../../../../platform/remote/common/remoteExtensionsScanner.js'; const defaultChat = { extensionId: product.defaultChatAgent?.extensionId ?? '', @@ -1022,6 +1023,7 @@ class ChatSetupContext extends Disposable { @IExtensionService private readonly extensionService: IExtensionService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IRemoteExtensionsScannerService private readonly remoteExtensionsScannerService: IRemoteExtensionsScannerService, @ILogService private readonly logService: ILogService ) { super(); @@ -1047,6 +1049,7 @@ class ChatSetupContext extends Disposable { } })); + await this.remoteExtensionsScannerService.whenExtensionsReady(); const extensions = await this.extensionManagementService.getInstalled(); const defaultChatExtension = extensions.find(value => ExtensionIdentifier.equals(value.identifier.id, defaultChat.extensionId)); this.update({ installed: !!defaultChatExtension && this.extensionEnablementService.isEnabled(defaultChatExtension) }); From dd86be1a95485a849a2003721b6e08235c46b031 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 18 Dec 2024 17:33:45 -0800 Subject: [PATCH 110/200] lists: support user selection in lists, and adopt it in the debug console (#236534) - Adds a `userSelection` option on lists that can be used to control behavior - Uses the DND logic for handling scrolling near the top and bottom - Preserved selected list elements while they're selected to ensure they can be accurately copied. - The DOM events we get around selection are pretty poor. I support mouse here but I'm unclear if/how touch events should be handled. ![](https://memes.peet.io/img/24-12-f06c680d-f209-476b-8945-b6fc33efe502.mp4) Fixes #228432, cc @joaomoreno --- src/vs/base/browser/ui/list/listView.ts | 98 +++++++++++++++++-- src/vs/base/browser/ui/list/listWidget.ts | 1 + .../workbench/contrib/debug/browser/repl.ts | 1 + 3 files changed, 94 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 00a19352e91ad..36355a377ba51 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DataTransfers, IDragAndDropData } from '../../dnd.js'; -import { $, addDisposableListener, animate, Dimension, getContentHeight, getContentWidth, getTopLeftOffset, getWindow, isAncestor, isHTMLElement, isSVGElement, scheduleAtNextAnimationFrame } from '../../dom.js'; +import { $, addDisposableListener, animate, Dimension, getContentHeight, getContentWidth, getDocument, getTopLeftOffset, getWindow, isAncestor, isHTMLElement, isSVGElement, scheduleAtNextAnimationFrame } from '../../dom.js'; import { DomEmitter } from '../../event.js'; import { IMouseWheelEvent } from '../../mouseEvent.js'; import { EventType as TouchEventType, Gesture, GestureEvent } from '../../touch.js'; @@ -82,6 +82,7 @@ export interface IListViewOptions extends IListViewOptionsUpdate { readonly setRowHeight?: boolean; readonly supportDynamicHeights?: boolean; readonly mouseSupport?: boolean; + readonly userSelection?: boolean; readonly accessibilityProvider?: IListViewAccessibilityProvider; readonly transformOptimization?: boolean; readonly alwaysConsumeMouseWheel?: boolean; @@ -105,7 +106,7 @@ const DefaultOptions = { horizontalScrolling: false, transformOptimization: true, alwaysConsumeMouseWheel: true, -}; +} satisfies IListViewOptions; export class ElementsDragAndDropData implements IDragAndDropData { @@ -321,6 +322,8 @@ export class ListView implements IListView { private currentDragFeedbackPosition: ListDragOverEffectPosition | undefined; private currentDragFeedbackDisposable: IDisposable = Disposable.None; private onDragLeaveTimeout: IDisposable = Disposable.None; + private currentSelectionDisposable: IDisposable = Disposable.None; + private currentSelectionBounds: IRange | undefined; private readonly disposables: DisposableStore = new DisposableStore(); @@ -369,7 +372,7 @@ export class ListView implements IListView { container: HTMLElement, private virtualDelegate: IListVirtualDelegate, renderers: IListRenderer[], - options: IListViewOptions = DefaultOptions as IListViewOptions + options: IListViewOptions = DefaultOptions ) { if (options.horizontalScrolling && options.supportDynamicHeights) { throw new Error('Horizontal scrolling and dynamic heights not supported simultaneously'); @@ -444,6 +447,12 @@ export class ListView implements IListView { this.disposables.add(addDisposableListener(this.domNode, 'drop', e => this.onDrop(this.toDragEvent(e)))); this.disposables.add(addDisposableListener(this.domNode, 'dragleave', e => this.onDragLeave(this.toDragEvent(e)))); this.disposables.add(addDisposableListener(this.domNode, 'dragend', e => this.onDragEnd(e))); + if (options.userSelection) { + if (options.dnd) { + throw new Error('DND and user selection cannot be used simultaneously'); + } + this.disposables.add(addDisposableListener(this.domNode, 'mousedown', e => this.onPotentialSelectionStart(e))); + } this.setRowLineHeight = options.setRowLineHeight ?? DefaultOptions.setRowLineHeight; this.setRowHeight = options.setRowHeight ?? DefaultOptions.setRowHeight; @@ -768,7 +777,7 @@ export class ListView implements IListView { } get firstVisibleIndex(): number { - const range = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); + const range = this.getVisibleRange(this.lastRenderTop, this.lastRenderHeight); return range.start; } @@ -1168,6 +1177,73 @@ export class ListView implements IListView { this.dnd.onDragStart?.(this.currentDragData, event); } + private onPotentialSelectionStart(e: MouseEvent) { + this.currentSelectionDisposable.dispose(); + const doc = getDocument(this.domNode); + + // Set up both the 'movement store' for watching the mouse, and the + // 'selection store' which lasts as long as there's a selection, even + // after the usr has stopped modifying it. + const selectionStore = this.currentSelectionDisposable = new DisposableStore(); + const movementStore = selectionStore.add(new DisposableStore()); + + // The selection events we get from the DOM are fairly limited and we lack a 'selection end' event. + // Selection events also don't tell us where the input doing the selection is. So, make a poor + // assumption that a user is using the mouse, and base our events on that. + movementStore.add(addDisposableListener(this.domNode, 'selectstart', () => { + this.setupDragAndDropScrollTopAnimation(e); + + movementStore.add(addDisposableListener(doc, 'mousemove', e => this.setupDragAndDropScrollTopAnimation(e))); + + // The selection is cleared either on mouseup if there's no selection, or on next mousedown + // when `this.currentSelectionDisposable` is reset. + selectionStore.add(toDisposable(() => { + const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); + this.currentSelectionBounds = undefined; + this.render(previousRenderRange, this.lastRenderTop, this.lastRenderHeight, undefined, undefined); + })); + selectionStore.add(addDisposableListener(doc, 'selectionchange', () => { + const selection = doc.getSelection(); + if (!selection) { + return; + } + + let start = this.getIndexOfListElement(selection.anchorNode as HTMLElement); + let end = this.getIndexOfListElement(selection.focusNode as HTMLElement); + if (start !== undefined && end !== undefined) { + if (end < start) { + [start, end] = [end, start]; + } + this.currentSelectionBounds = { start, end }; + } + })); + })); + + movementStore.add(addDisposableListener(doc, 'mouseup', () => { + movementStore.dispose(); + + if (doc.getSelection()?.isCollapsed !== false) { + selectionStore.dispose(); + } + })); + } + + private getIndexOfListElement(element: HTMLElement | null): number | undefined { + if (!element || !this.domNode.contains(element)) { + return undefined; + } + + while (element && element !== this.domNode) { + if (element.dataset?.index) { + return Number(element.dataset.index); + } + + element = element.parentElement; + } + + return undefined; + } + private onDragOver(event: IListDragEvent): boolean { event.browserEvent.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome) @@ -1327,7 +1403,7 @@ export class ListView implements IListView { // DND scroll top animation - private setupDragAndDropScrollTopAnimation(event: DragEvent): void { + private setupDragAndDropScrollTopAnimation(event: DragEvent | MouseEvent): void { if (!this.dragOverAnimationDisposable) { const viewTop = getTopLeftOffset(this.domNode).top; this.dragOverAnimationDisposable = animate(getWindow(this.domNode), this.animateDragAndDropScrollTop.bind(this, viewTop)); @@ -1401,13 +1477,23 @@ export class ListView implements IListView { return undefined; } - protected getRenderRange(renderTop: number, renderHeight: number): IRange { + private getVisibleRange(renderTop: number, renderHeight: number): IRange { return { start: this.rangeMap.indexAt(renderTop), end: this.rangeMap.indexAfter(renderTop + renderHeight - 1) }; } + protected getRenderRange(renderTop: number, renderHeight: number): IRange { + const range = this.getVisibleRange(renderTop, renderHeight); + if (this.currentSelectionBounds) { + range.start = Math.min(range.start, this.currentSelectionBounds.start); + range.end = Math.max(range.end, this.currentSelectionBounds.end + 1); + } + + return range; + } + /** * Given a stable rendered state, checks every rendered element whether it needs * to be probed for dynamic height. Adjusts scroll height and top if necessary. diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index ae9d5a46fb47e..78932044f96ed 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -1056,6 +1056,7 @@ export interface IListOptions extends IListOptionsUpdate { readonly setRowHeight?: boolean; readonly supportDynamicHeights?: boolean; readonly mouseSupport?: boolean; + readonly userSelection?: boolean; readonly horizontalScrolling?: boolean; readonly scrollByPage?: boolean; readonly transformOptimization?: boolean; diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 4bcfe48be3e07..a9959c5d1c53e 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -667,6 +667,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { filter: this.filter, accessibilityProvider: new ReplAccessibilityProvider(), identityProvider, + userSelection: true, mouseSupport: false, findWidgetEnabled: true, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IReplElement) => e.toString(true) }, From acd32b17b837b05a64275c297949753df46dbe6d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 18 Dec 2024 20:17:02 -0800 Subject: [PATCH 111/200] Use IExtensionsWorkbenchService instead (#236540) Use IExtensionsWorkbenchService --- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index f42fd26ada529..a325d5526f155 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -26,7 +26,6 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.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 } from '../../../../platform/log/common/log.js'; @@ -60,7 +59,6 @@ import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { URI } from '../../../../base/common/uri.js'; import { IHostService } from '../../../services/host/browser/host.js'; import Severity from '../../../../base/common/severity.js'; -import { IRemoteExtensionsScannerService } from '../../../../platform/remote/common/remoteExtensionsScanner.js'; const defaultChat = { extensionId: product.defaultChatAgent?.extensionId ?? '', @@ -1021,10 +1019,9 @@ class ChatSetupContext extends Disposable { @IStorageService private readonly storageService: IStorageService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IExtensionService private readonly extensionService: IExtensionService, - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, - @IRemoteExtensionsScannerService private readonly remoteExtensionsScannerService: IRemoteExtensionsScannerService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, ) { super(); @@ -1049,10 +1046,9 @@ class ChatSetupContext extends Disposable { } })); - await this.remoteExtensionsScannerService.whenExtensionsReady(); - const extensions = await this.extensionManagementService.getInstalled(); + const extensions = await this.extensionsWorkbenchService.queryLocal(); const defaultChatExtension = extensions.find(value => ExtensionIdentifier.equals(value.identifier.id, defaultChat.extensionId)); - this.update({ installed: !!defaultChatExtension && this.extensionEnablementService.isEnabled(defaultChatExtension) }); + this.update({ installed: !!defaultChatExtension?.local && this.extensionEnablementService.isEnabled(defaultChatExtension.local) }); } update(context: { installed: boolean }): Promise; From d47f63e5dcde69292e29018bd6fbb4bc93ccaaf1 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:35:34 +0100 Subject: [PATCH 112/200] Revert focus behavior fix for views/panels (#236556) Revert "Fix inconsistent focus behavior when toggling views/panels (#235622)" This reverts commit d5746c5593f6afe15e44a9f7877a064df6605d11. --- src/vs/workbench/browser/layout.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 592bb4146af11..bf1f54199d13a 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1783,9 +1783,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi else if (!hidden && !this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)) { const viewletToOpen = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.Sidebar); if (viewletToOpen) { - const viewlet = this.paneCompositeService.openPaneComposite(viewletToOpen, ViewContainerLocation.Sidebar); + const viewlet = this.paneCompositeService.openPaneComposite(viewletToOpen, ViewContainerLocation.Sidebar, true); if (!viewlet) { - this.paneCompositeService.openPaneComposite(this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id, ViewContainerLocation.Sidebar); + this.paneCompositeService.openPaneComposite(this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id, ViewContainerLocation.Sidebar, true); } } } @@ -1931,7 +1931,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } if (panelToOpen) { - this.paneCompositeService.openPaneComposite(panelToOpen, ViewContainerLocation.Panel); + const focus = !skipLayout; + this.paneCompositeService.openPaneComposite(panelToOpen, ViewContainerLocation.Panel, focus); } } @@ -2030,7 +2031,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } if (panelToOpen) { - this.paneCompositeService.openPaneComposite(panelToOpen, ViewContainerLocation.AuxiliaryBar); + const focus = !skipLayout; + this.paneCompositeService.openPaneComposite(panelToOpen, ViewContainerLocation.AuxiliaryBar, focus); } } From d74499bdb99fd78fb4c6ef819e9c76fa766cae1e Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Thu, 19 Dec 2024 08:23:06 +0100 Subject: [PATCH 113/200] @vscode/proxy-agent 0.28.0 --- package-lock.json | 8 ++++---- package.json | 2 +- remote/package-lock.json | 8 ++++---- remote/package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 60dc545ca3754..5f2b14c3e3973 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.1.8", - "@vscode/proxy-agent": "^0.27.0", + "@vscode/proxy-agent": "^0.28.0", "@vscode/ripgrep": "^1.15.9", "@vscode/spdlog": "^0.15.0", "@vscode/sqlite3": "5.1.8-vscode", @@ -2848,9 +2848,9 @@ } }, "node_modules/@vscode/proxy-agent": { - "version": "0.27.0", - "resolved": "git+ssh://git@github.com/microsoft/vscode-proxy-agent.git#0803e0a7d1249bcb64ae9d256d0ee732b7246ea5", - "integrity": "sha512-ID1MOlynRkVN121n85Hs1enjW16B5Z1O6bMDkSPqvAMVD6eHUOqmgPZuEkBbRI43PfA6boUEY9Ndjp+0wPKtsg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.28.0.tgz", + "integrity": "sha512-7rYF8ju0dP/ASpjjnuOCvzRosGLoKz0WOyNohREUskRdrvMEnYuEUXy84lHlH+4+MD8CZZjw2SUzhjHaJK1hxg==", "license": "MIT", "dependencies": { "@tootallnate/once": "^3.0.0", diff --git a/package.json b/package.json index 8dd5e0a86a37b..3316ae225ea89 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.1.8", - "@vscode/proxy-agent": "^0.27.0", + "@vscode/proxy-agent": "^0.28.0", "@vscode/ripgrep": "^1.15.9", "@vscode/spdlog": "^0.15.0", "@vscode/sqlite3": "5.1.8-vscode", diff --git a/remote/package-lock.json b/remote/package-lock.json index dfbff101a88fa..3cf2293986152 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -13,7 +13,7 @@ "@parcel/watcher": "2.5.0", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.27.0", + "@vscode/proxy-agent": "^0.28.0", "@vscode/ripgrep": "^1.15.9", "@vscode/spdlog": "^0.15.0", "@vscode/tree-sitter-wasm": "^0.0.4", @@ -418,9 +418,9 @@ "integrity": "sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg==" }, "node_modules/@vscode/proxy-agent": { - "version": "0.27.0", - "resolved": "git+ssh://git@github.com/microsoft/vscode-proxy-agent.git#0803e0a7d1249bcb64ae9d256d0ee732b7246ea5", - "integrity": "sha512-ID1MOlynRkVN121n85Hs1enjW16B5Z1O6bMDkSPqvAMVD6eHUOqmgPZuEkBbRI43PfA6boUEY9Ndjp+0wPKtsg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.28.0.tgz", + "integrity": "sha512-7rYF8ju0dP/ASpjjnuOCvzRosGLoKz0WOyNohREUskRdrvMEnYuEUXy84lHlH+4+MD8CZZjw2SUzhjHaJK1hxg==", "license": "MIT", "dependencies": { "@tootallnate/once": "^3.0.0", diff --git a/remote/package.json b/remote/package.json index b5ebb761e0931..5a61a15c0513c 100644 --- a/remote/package.json +++ b/remote/package.json @@ -8,7 +8,7 @@ "@parcel/watcher": "2.5.0", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.27.0", + "@vscode/proxy-agent": "^0.28.0", "@vscode/ripgrep": "^1.15.9", "@vscode/spdlog": "^0.15.0", "@vscode/tree-sitter-wasm": "^0.0.4", From 7e000daa484bbf6e434c9942c39bb2eee569619c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 19 Dec 2024 10:55:10 +0100 Subject: [PATCH 114/200] recovery fix: fixing installing extensions everywhere when it is already installed locally (#236562) * recovery fix: fixing installing extensions everywhere when it is already installed locally * clean up --- .../extensions/browser/extensionsWorkbenchService.ts | 8 +++++--- .../common/extensionManagementService.ts | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index ae7df7d09b38c..0967e7d34c7fc 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -2368,8 +2368,9 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (extension?.isMalicious) { throw new Error(nls.localize('malicious', "This extension is reported to be problematic.")); } + // TODO: @sandy081 - Install the extension only on servers where it is not installed // Do not install if requested to enable and extension is already installed - if (!(installOptions.enable && extension?.local)) { + if (installOptions.installEverywhere || !(installOptions.enable && extension?.local)) { if (!installable) { if (!gallery) { const id = isString(arg) ? arg : (arg).identifier.id; @@ -2730,10 +2731,11 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return this.extensionManagementService.installVSIX(vsix, manifest, installOptions); } - private installFromGallery(extension: IExtension, gallery: IGalleryExtension, installOptions?: InstallOptions): Promise { + private installFromGallery(extension: IExtension, gallery: IGalleryExtension, installOptions?: InstallExtensionOptions): Promise { installOptions = installOptions ?? {}; installOptions.pinned = extension.local?.pinned || !this.shouldAutoUpdateExtension(extension); - if (extension.local) { + // TODO: @sandy081 - Install the extension only on servers where it is not installed + if (!installOptions.installEverywhere && extension.local) { installOptions.productVersion = this.getProductVersion(); installOptions.operation = InstallOperation.Update; return this.extensionManagementService.updateFromGallery(gallery, extension.local, installOptions); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index d82368283243a..bc0a4687128c2 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -466,7 +466,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench installOptions = { ...(installOptions || {}), isMachineScoped }; } - if (installOptions.installEverywhere || (!installOptions.isMachineScoped && this.isExtensionsSyncEnabled())) { + if (!installOptions.isMachineScoped && this.isExtensionsSyncEnabled()) { if (this.extensionManagementServerService.localExtensionManagementServer && !servers.includes(this.extensionManagementServerService.localExtensionManagementServer) && await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall(gallery) === true) { @@ -597,7 +597,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench } } - private async validateAndGetExtensionManagementServersToInstall(gallery: IGalleryExtension, installOptions?: InstallOptions): Promise { + private async validateAndGetExtensionManagementServersToInstall(gallery: IGalleryExtension, installOptions?: IWorkbenchInstallOptions): Promise { const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None); if (!manifest) { @@ -606,8 +606,8 @@ export class ExtensionManagementService extends Disposable implements IWorkbench const servers: IExtensionManagementServer[] = []; - // Install Language pack on local and remote servers - if (isLanguagePackExtension(manifest)) { + // Install everywhere if asked to install everywhere or if the extension is a language pack + if (installOptions?.installEverywhere || isLanguagePackExtension(manifest)) { servers.push(...this.servers.filter(server => server !== this.extensionManagementServerService.webExtensionManagementServer)); } else { const server = this.getExtensionManagementServerToInstall(manifest); From 7efdaa5e8eadfdb97428e86c0ade801d22ab5868 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 19 Dec 2024 11:34:50 +0100 Subject: [PATCH 115/200] don't show inline chat hint when line has too many comments or strings. (#236567) The limit is 25% strings, comments, or regex token and (as before) lines ending in comments https://github.com/microsoft/vscode-copilot-release/issues/3009 --- .../browser/inlineChatCurrentLine.ts | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts index 43e73454efd11..08c9911fdb800 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts @@ -140,14 +140,32 @@ export class ShowInlineChatHintAction extends EditorAction2 { model.tokenization.forceTokenization(position.lineNumber); const tokens = model.tokenization.getLineTokens(position.lineNumber); - const tokenIndex = tokens.findTokenIndexAtOffset(position.column - 1); - const tokenType = tokens.getStandardTokenType(tokenIndex); - if (tokenType === StandardTokenType.Comment) { + let totalLength = 0; + let specialLength = 0; + let lastTokenType: StandardTokenType | undefined; + + tokens.forEach(idx => { + const tokenType = tokens.getStandardTokenType(idx); + const startOffset = tokens.getStartOffset(idx); + const endOffset = tokens.getEndOffset(idx); + totalLength += endOffset - startOffset; + + if (tokenType !== StandardTokenType.Other) { + specialLength += endOffset - startOffset; + } + lastTokenType = tokenType; + }); + + if (specialLength / totalLength > 0.25) { ctrl.hide(); - } else { - ctrl.show(); + return; + } + if (lastTokenType === StandardTokenType.Comment) { + ctrl.hide(); + return; } + ctrl.show(); } } From cbfd8ab51359eafff1b5258f25dc334b2d9e26e5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 19 Dec 2024 02:50:00 -0800 Subject: [PATCH 116/200] Cache builtin commands and get all global commands for pwsh This should make things quite a bit faster for expensive fetching of commands. This also caches empty builtin commands so it's not attempted every time. Fixes #236097 Part of #235024 --- .../src/terminalSuggestMain.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 4acd5b59f8a00..13ff032037ac7 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -12,14 +12,14 @@ import codeCompletionSpec from './completions/code'; import cdSpec from './completions/cd'; let cachedAvailableCommands: Set | undefined; -let cachedBuiltinCommands: Map | undefined; +const cachedBuiltinCommands: Map = new Map(); export const availableSpecs = [codeCompletionSpec, codeInsidersCompletionSpec, cdSpec]; function getBuiltinCommands(shell: string): string[] | undefined { try { const shellType = path.basename(shell, path.extname(shell)); - const cachedCommands = cachedBuiltinCommands?.get(shellType); + const cachedCommands = cachedBuiltinCommands.get(shellType); if (cachedCommands) { return cachedCommands; } @@ -38,14 +38,14 @@ function getBuiltinCommands(shell: string): string[] | undefined { break; } case 'fish': { - // TODO: ghost text in the command line prevents - // completions from working ATM for fish + // TODO: Ghost text in the command line prevents completions from working ATM for fish const fishOutput = execSync('functions -n', options); commands = fishOutput.split(', ').filter(filter); break; } case 'pwsh': { - const output = execSync('Get-Command | Select-Object Name, CommandType, DisplayName | ConvertTo-Json', options); + // TODO: Select `CommandType, DisplayName` and map to a rich type with kind and detail + const output = execSync('Get-Command -All | Select-Object Name | ConvertTo-Json', options); let json: any; try { json = JSON.parse(output); @@ -53,17 +53,12 @@ function getBuiltinCommands(shell: string): string[] | undefined { console.error('Error parsing pwsh output:', e); return []; } - // TODO: Return a rich type with kind and detail commands = (json as any[]).map(e => e.Name); break; } } - // TODO: Cache failure results too - if (commands?.length) { - cachedBuiltinCommands?.set(shellType, commands); - return commands; - } - return; + cachedBuiltinCommands.set(shellType, commands); + return commands; } catch (error) { console.error('Error fetching builtin commands:', error); From 011e8ec6df9cfd2aef2da84a617d4489fb9f372a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 19 Dec 2024 11:56:01 +0100 Subject: [PATCH 117/200] chat setup - use 1 service as source of truth for extensions (#236564) --- .../contrib/chat/browser/chatSetup.ts | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index a325d5526f155..3e93dbf6375bd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -43,7 +43,6 @@ import { IViewDescriptorService, ViewContainerLocation } from '../../../common/v import { IActivityService, ProgressBadge } from '../../../services/activity/common/activity.js'; import { AuthenticationSession, IAuthenticationExtensionsService, IAuthenticationService } from '../../../services/authentication/common/authentication.js'; import { IWorkbenchExtensionEnablementService } from '../../../services/extensionManagement/common/extensionManagement.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'; @@ -1018,7 +1017,6 @@ class ChatSetupContext extends Disposable { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IStorageService private readonly storageService: IStorageService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IExtensionService private readonly extensionService: IExtensionService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @ILogService private readonly logService: ILogService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @@ -1030,25 +1028,19 @@ class ChatSetupContext extends Disposable { } private async checkExtensionInstallation(): Promise { - this._register(this.extensionService.onDidChangeExtensions(result => { - for (const extension of result.removed) { - if (ExtensionIdentifier.equals(defaultChat.extensionId, extension.identifier)) { - this.update({ installed: false }); - break; - } - } - for (const extension of result.added) { - if (ExtensionIdentifier.equals(defaultChat.extensionId, extension.identifier)) { - this.update({ installed: true }); - break; - } + // Await extensions to be ready to be queries + await this.extensionsWorkbenchService.queryLocal(); + + // Listen to change and process extensions once + this._register(Event.runAndSubscribe(this.extensionsWorkbenchService.onChange, (e) => { + if (e && !ExtensionIdentifier.equals(e.identifier.id, defaultChat.extensionId)) { + return; // unrelated event } - })); - const extensions = await this.extensionsWorkbenchService.queryLocal(); - const defaultChatExtension = extensions.find(value => ExtensionIdentifier.equals(value.identifier.id, defaultChat.extensionId)); - this.update({ installed: !!defaultChatExtension?.local && this.extensionEnablementService.isEnabled(defaultChatExtension.local) }); + const defaultChatExtension = this.extensionsWorkbenchService.local.find(value => ExtensionIdentifier.equals(value.identifier.id, defaultChat.extensionId)); + this.update({ installed: !!defaultChatExtension?.local && this.extensionEnablementService.isEnabled(defaultChatExtension.local) }); + })); } update(context: { installed: boolean }): Promise; From 7f512c528d36154fe0cab63dcd75b505013fff44 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 19 Dec 2024 12:13:57 +0100 Subject: [PATCH 118/200] chat - fix setup in web based on remote connection --- .../chat/browser/actions/chatActions.ts | 19 ++++++++++++++----- .../contrib/chat/browser/chat.contribution.ts | 2 ++ .../contrib/chat/browser/chatSetup.ts | 10 ++++++++-- .../contrib/chat/common/chatContextKeys.ts | 4 ++++ .../electron-sandbox/chat.contribution.ts | 2 -- .../common/gettingStartedContent.ts | 7 +++---- 6 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index a0bb888eb80f1..d1d7d951559fa 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -9,7 +9,7 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { fromNowByDay } from '../../../../../base/common/date.js'; import { Event } from '../../../../../base/common/event.js'; import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; -import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, markAsSingleton } 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'; @@ -531,7 +531,10 @@ MenuRegistry.appendMenuItem(MenuId.CommandCenter, { submenu: MenuId.ChatCommandCenter, title: localize('title4', "Chat"), icon: Codicon.copilot, - when: ContextKeyExpr.has('config.chat.commandCenter.enabled'), + when: ContextKeyExpr.and( + ChatContextKeys.supported, + ContextKeyExpr.has('config.chat.commandCenter.enabled') + ), order: 10001, }); @@ -541,7 +544,10 @@ registerAction2(class ToggleCopilotControl extends ToggleTitleBarConfigAction { 'chat.commandCenter.enabled', localize('toggle.chatControl', 'Copilot Controls'), localize('toggle.chatControlsDescription', "Toggle visibility of the Copilot Controls in title bar"), 4, false, - ContextKeyExpr.has('config.window.commandCenter') + ContextKeyExpr.and( + ChatContextKeys.supported, + ContextKeyExpr.has('config.window.commandCenter') + ) ); } }); @@ -561,7 +567,7 @@ export class ChatCommandCenterRendering extends Disposable implements IWorkbench const contextKeySet = new Set([ChatContextKeys.Setup.signedOut.key]); - this._store.add(actionViewItemService.register(MenuId.CommandCenter, MenuId.ChatCommandCenter, (action, options) => { + const disposable = actionViewItemService.register(MenuId.CommandCenter, MenuId.ChatCommandCenter, (action, options) => { if (!(action instanceof SubmenuItemAction)) { return undefined; } @@ -607,6 +613,9 @@ export class ChatCommandCenterRendering extends Disposable implements IWorkbench agentService.onDidChangeAgents, chatQuotasService.onDidChangeQuotas, Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(contextKeySet)) - ))); + )); + + // Reduces flicker a bit on reload/restart + markAsSingleton(disposable); } } diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index b03d4605d160e..456c8817cd46e 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -81,6 +81,7 @@ import { Extensions, IConfigurationMigrationRegistry } from '../../../common/con import { ChatEditorOverlayController } from './chatEditorOverlay.js'; import { ChatRelatedFilesContribution } from './contrib/chatInputRelatedFilesContrib.js'; import { ChatQuotasService, ChatQuotasStatusBarEntry, IChatQuotasService } from './chatQuotasService.js'; +import { ChatSetupContribution } from './chatSetup.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -316,6 +317,7 @@ registerWorkbenchContribution2(ChatEditorSaving.ID, ChatEditorSaving, WorkbenchP registerWorkbenchContribution2(ChatEditorAutoSaveDisabler.ID, ChatEditorAutoSaveDisabler, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2(ChatViewsWelcomeHandler.ID, ChatViewsWelcomeHandler, WorkbenchPhase.BlockStartup); registerWorkbenchContribution2(ChatGettingStartedContribution.ID, ChatGettingStartedContribution, WorkbenchPhase.Eventually); +registerWorkbenchContribution2(ChatSetupContribution.ID, ChatSetupContribution, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2(ChatQuotasStatusBarEntry.ID, ChatQuotasStatusBarEntry, WorkbenchPhase.Eventually); registerChatActions(); diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 3e93dbf6375bd..53a86ec5245ad 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -58,6 +58,8 @@ import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { URI } from '../../../../base/common/uri.js'; import { IHostService } from '../../../services/host/browser/host.js'; import Severity from '../../../../base/common/severity.js'; +import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; +import { isWeb } from '../../../../base/common/platform.js'; const defaultChat = { extensionId: product.defaultChatAgent?.extensionId ?? '', @@ -106,11 +108,15 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr constructor( @IProductService private readonly productService: IProductService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { super(); - if (!this.productService.defaultChatAgent) { + if ( + !this.productService.defaultChatAgent || // needs product config + (isWeb && !this.environmentService.remoteAuthority) // only enabled locally or a remote backend + ) { return; } diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index f615233de5050..be03126dd2dbc 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -5,6 +5,8 @@ import { localize } from '../../../../nls.js'; import { ContextKeyExpr, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { IsWebContext } from '../../../../platform/contextkey/common/contextkeys.js'; +import { RemoteNameContext } from '../../../common/contextkeys.js'; import { ChatAgentLocation } from './chatAgents.js'; export namespace ChatContextKeys { @@ -27,7 +29,9 @@ export namespace ChatContextKeys { 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 supported = ContextKeyExpr.or(IsWebContext.toNegated(), RemoteNameContext.notEqualsTo('')); // supported on desktop and in web only with a remote connection 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.") }); diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts index e71f4342b157d..0e888b7f86443 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts @@ -6,7 +6,6 @@ import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction, KeywordActivationContribution, InstallSpeechProviderForVoiceChatAction, HoldToVoiceChatInChatViewAction, ReadChatResponseAloud, StopReadAloud, StopReadChatItemAloud } from './actions/voiceChatActions.js'; import { registerAction2 } from '../../../../platform/actions/common/actions.js'; import { WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js'; -import { ChatSetupContribution } from '../browser/chatSetup.js'; registerAction2(StartVoiceChatAction); registerAction2(InstallSpeechProviderForVoiceChatAction); @@ -24,4 +23,3 @@ registerAction2(StopReadChatItemAloud); registerAction2(StopReadAloud); registerWorkbenchContribution2(KeywordActivationContribution.ID, KeywordActivationContribution, WorkbenchPhase.AfterRestored); -registerWorkbenchContribution2(ChatSetupContribution.ID, ChatSetupContribution, WorkbenchPhase.BlockRestore); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts index 7fd3e30163f35..53c8ae8051134 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts @@ -282,7 +282,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ // id: 'settings', // title: localize('gettingStarted.settings.title', "Tune your settings"), // description: localize('gettingStarted.settings.description.interpolated', "Customize every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), - // when: '!config.chat.experimental.offerSetup', // media: { // type: 'svg', altText: 'VS Code Settings', path: 'settings.svg' // }, @@ -291,7 +290,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ // id: 'settingsSync', // title: localize('gettingStarted.settingsSync.title', "Sync settings across devices"), // description: localize('gettingStarted.settingsSync.description.interpolated', "Keep your essential customizations backed up and updated across all your devices.\n{0}", Button(localize('enableSync', "Backup and Sync Settings"), 'command:workbench.userDataSync.actions.turnOn')), - // when: '!config.chat.experimental.offerSetup && syncStatus != uninitialized', + // when: 'syncStatus != uninitialized', // completionEvents: ['onEvent:sync-enabled'], // media: { // type: 'svg', altText: 'The "Turn on Sync" entry in the settings gear menu.', path: 'settingsSync.svg' @@ -318,7 +317,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ // id: 'pickAFolderTask-Mac', // title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), // description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFileFolder')), - // when: '!config.chat.experimental.offerSetup && isMac && workspaceFolderCount == 0', + // when: 'isMac && workspaceFolderCount == 0', // media: { // type: 'svg', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: 'openFolder.svg' // } @@ -327,7 +326,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ // id: 'pickAFolderTask-Other', // title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), // description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFolder')), - // when: '!config.chat.experimental.offerSetup && !isMac && workspaceFolderCount == 0', + // when: '!isMac && workspaceFolderCount == 0', // media: { // type: 'svg', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: 'openFolder.svg' // } From 22959031c000aa613a72c6336dda2487e4d514a2 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:23:45 +0100 Subject: [PATCH 119/200] Enable dragging of editor from breadcrumbs in single tabs (#236571) drag editor from single tab breadcrumbs --- .../browser/parts/editor/breadcrumbsControl.ts | 16 +++++++++++----- .../browser/parts/editor/breadcrumbsModel.ts | 2 +- .../browser/parts/editor/editorTitleControl.ts | 3 ++- .../parts/editor/singleEditorTabsControl.ts | 3 ++- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 51ca71f42d140..9a2581907c412 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -25,7 +25,7 @@ import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/c 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 { fillInSymbolsDragData } from '../../../../platform/dnd/browser/dnd.js'; +import { fillInSymbolsDragData, LocalSelectionTransfer } 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 { InstantiationService } from '../../../../platform/instantiation/common/instantiationService.js'; @@ -39,7 +39,7 @@ import { EditorResourceAccessor, IEditorPartOptions, SideBySideEditor } from '.. 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 { fillEditorsDragData } from '../../dnd.js'; +import { DraggedEditorIdentifier, fillEditorsDragData } from '../../dnd.js'; import { DEFAULT_LABELS_CONTAINER, ResourceLabels } from '../../labels.js'; import { BreadcrumbsConfig, IBreadcrumbsService } from './breadcrumbs.js'; import { BreadcrumbsModel, FileElement, OutlineElement2 } from './breadcrumbsModel.js'; @@ -105,7 +105,7 @@ class OutlineItem extends BreadcrumbsItem { this._disposables.add(toDisposable(() => { renderer.disposeTemplate(template); })); if (element instanceof OutlineElement && outline.uri) { - this._disposables.add(this._instantiationService.invokeFunction(accessor => createBreadcrumbDndObserver(accessor, container, element.symbol.name, { symbol: element.symbol, uri: outline.uri! }))); + this._disposables.add(this._instantiationService.invokeFunction(accessor => createBreadcrumbDndObserver(accessor, container, element.symbol.name, { symbol: element.symbol, uri: outline.uri! }, this.model, this.options.dragEditor))); } } } @@ -151,12 +151,12 @@ class FileItem extends BreadcrumbsItem { container.classList.add(FileKind[this.element.kind].toLowerCase()); this._disposables.add(label); - this._disposables.add(this._instantiationService.invokeFunction(accessor => createBreadcrumbDndObserver(accessor, container, basename(this.element.uri), this.element.uri))); + this._disposables.add(this._instantiationService.invokeFunction(accessor => createBreadcrumbDndObserver(accessor, container, basename(this.element.uri), this.element.uri, this.model, this.options.dragEditor))); } } -function createBreadcrumbDndObserver(accessor: ServicesAccessor, container: HTMLElement, label: string, item: URI | { symbol: DocumentSymbol; uri: URI }): IDisposable { +function createBreadcrumbDndObserver(accessor: ServicesAccessor, container: HTMLElement, label: string, item: URI | { symbol: DocumentSymbol; uri: URI }, model: BreadcrumbsModel, dragEditor: boolean): IDisposable { const instantiationService = accessor.get(IInstantiationService); container.draggable = true; @@ -183,6 +183,11 @@ function createBreadcrumbDndObserver(accessor: ServicesAccessor, container: HTML kind: item.symbol.kind }], event); } + + if (dragEditor && model.editor && model.editor?.input) { + const editorTransfer = LocalSelectionTransfer.getInstance(); + editorTransfer.setData([new DraggedEditorIdentifier({ editor: model.editor.input, groupId: model.editor.group.id })], DraggedEditorIdentifier.prototype); + } }); // Create drag image and remove when dropped @@ -209,6 +214,7 @@ export interface IBreadcrumbsControlOptions { readonly showSymbolIcons: boolean; readonly showDecorationColors: boolean; readonly showPlaceholder: boolean; + readonly dragEditor: boolean; readonly widgetStyles?: IBreadcrumbsWidgetStyles; } diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index f49b2301ea506..1c4aaef090abe 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -49,7 +49,7 @@ export class BreadcrumbsModel { constructor( readonly resource: URI, - editor: IEditorPane | undefined, + readonly editor: IEditorPane | undefined, @IConfigurationService configurationService: IConfigurationService, @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, @IOutlineService private readonly _outlineService: IOutlineService, diff --git a/src/vs/workbench/browser/parts/editor/editorTitleControl.ts b/src/vs/workbench/browser/parts/editor/editorTitleControl.ts index ac74f9b772845..65ef9fca411f1 100644 --- a/src/vs/workbench/browser/parts/editor/editorTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTitleControl.ts @@ -90,7 +90,8 @@ export class EditorTitleControl extends Themable { showFileIcons: true, showSymbolIcons: true, showDecorationColors: false, - showPlaceholder: true + showPlaceholder: true, + dragEditor: false, })); // Breadcrumbs enablement & visibility change have an impact on layout diff --git a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts index b8d31c0d171a5..a3b16b2d7fcc5 100644 --- a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts @@ -60,7 +60,8 @@ export class SingleEditorTabsControl extends EditorTabsControl { showSymbolIcons: true, showDecorationColors: false, widgetStyles: { ...defaultBreadcrumbsWidgetStyles, breadcrumbsBackground: Color.transparent.toString() }, - showPlaceholder: false + showPlaceholder: false, + dragEditor: true, })); this._register(this.breadcrumbsControlFactory.onDidEnablementChange(() => this.handleBreadcrumbsEnablementChange())); titleContainer.classList.toggle('breadcrumbs', Boolean(this.breadcrumbsControl)); From 1b078e1a20c122f11d4df60dd836f9ee2f87c2f6 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 19 Dec 2024 12:42:58 +0100 Subject: [PATCH 120/200] Disable word wrap in preview editor (#236574) --- .../browser/view/inlineEdits/sideBySideDiff.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts index 5f36bec5b3367..b34dc44ac1df7 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts @@ -268,6 +268,8 @@ export class InlineEditsSideBySideDiff extends Disposable { }, readOnly: true, wordWrap: 'off', + wordWrapOverride1: 'off', + wordWrapOverride2: 'off', }, { contributions: [], }, this._editor From b392dd9337e935d639d6a7b18ae02164eda7c283 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 19 Dec 2024 12:51:27 +0100 Subject: [PATCH 121/200] simplification: tokenizeLineWithEdit -> tokenizeLinesAt (#236577) --- src/vs/editor/common/core/offsetEdit.ts | 12 ++++++ src/vs/editor/common/model/textModelTokens.ts | 38 ++++++------------- .../common/model/tokenizationTextModelPart.ts | 13 ++++--- src/vs/editor/common/model/tokens.ts | 4 +- .../editor/common/model/treeSitterTokens.ts | 5 +-- .../common/tokenizationTextModelPart.ts | 31 +-------------- src/vs/editor/common/tokens/lineTokens.ts | 4 ++ src/vs/editor/common/tokens/tokenArray.ts | 25 ++++++++++++ .../browser/model/provideInlineCompletions.ts | 18 ++++----- .../browser/view/ghostText/ghostTextView.ts | 11 ++---- 10 files changed, 77 insertions(+), 84 deletions(-) diff --git a/src/vs/editor/common/core/offsetEdit.ts b/src/vs/editor/common/core/offsetEdit.ts index 426f90d22b655..d6886ee5fae23 100644 --- a/src/vs/editor/common/core/offsetEdit.ts +++ b/src/vs/editor/common/core/offsetEdit.ts @@ -228,6 +228,10 @@ export class SingleOffsetEdit { return new SingleOffsetEdit(OffsetRange.emptyAt(offset), text); } + public static replace(range: OffsetRange, text: string): SingleOffsetEdit { + return new SingleOffsetEdit(range, text); + } + constructor( public readonly replaceRange: OffsetRange, public readonly newText: string, @@ -240,6 +244,14 @@ export class SingleOffsetEdit { get isEmpty() { return this.newText.length === 0 && this.replaceRange.length === 0; } + + apply(str: string): string { + return str.substring(0, this.replaceRange.start) + this.newText + str.substring(this.replaceRange.endExclusive); + } + + getRangeAfterApply(): OffsetRange { + return new OffsetRange(this.replaceRange.start, this.replaceRange.start + this.newText.length); + } } /** diff --git a/src/vs/editor/common/model/textModelTokens.ts b/src/vs/editor/common/model/textModelTokens.ts index 85068d38984c4..7dbbc93a13107 100644 --- a/src/vs/editor/common/model/textModelTokens.ts +++ b/src/vs/editor/common/model/textModelTokens.ts @@ -17,7 +17,6 @@ import { nullTokenizeEncoded } from '../languages/nullTokenize.js'; import { ITextModel } from '../model.js'; import { FixedArray } from './fixedArray.js'; import { IModelContentChange } from '../textModelEvents.js'; -import { ITokenizeLineWithEditResult, LineEditWithAdditionalLines } from '../tokenizationTextModelPart.js'; import { ContiguousMultilineTokensBuilder } from '../tokens/contiguousMultilineTokensBuilder.js'; import { LineTokens } from '../tokens/lineTokens.js'; @@ -102,38 +101,23 @@ export class TokenizerWithStateStoreAndTextModel } /** assumes state is up to date */ - public tokenizeLineWithEdit(lineNumber: number, edit: LineEditWithAdditionalLines): ITokenizeLineWithEditResult { - const lineStartState = this.getStartState(lineNumber); + public tokenizeLinesAt(lineNumber: number, lines: string[]): LineTokens[] | null { + const lineStartState: IState | null = this.getStartState(lineNumber); if (!lineStartState) { - return { mainLineTokens: null, additionalLines: null }; + return null; } - const curLineContent = this._textModel.getLineContent(lineNumber); - const newLineContent = edit.lineEdit.apply(curLineContent); - - const languageId = this._textModel.getLanguageIdAtPosition(lineNumber, 0); - const result = safeTokenize( - this._languageIdCodec, - languageId, - this.tokenizationSupport, - newLineContent, - true, - lineStartState - ); + const languageId = this._textModel.getLanguageId(); + const result: LineTokens[] = []; - let additionalLines: LineTokens[] | null = null; - if (edit.additionalLines) { - additionalLines = []; - let state = result.endState; - for (const line of edit.additionalLines) { - const r = safeTokenize(this._languageIdCodec, languageId, this.tokenizationSupport, line, true, state); - additionalLines.push(new LineTokens(r.tokens, line, this._languageIdCodec)); - state = r.endState; - } + let state = lineStartState; + for (const line of lines) { + const r = safeTokenize(this._languageIdCodec, languageId, this.tokenizationSupport, line, true, state); + result.push(new LineTokens(r.tokens, line, this._languageIdCodec)); + state = r.endState; } - const mainLineTokens = new LineTokens(result.tokens, newLineContent, this._languageIdCodec); - return { mainLineTokens, additionalLines }; + return result; } public hasAccurateTokensForLine(lineNumber: number): boolean { diff --git a/src/vs/editor/common/model/tokenizationTextModelPart.ts b/src/vs/editor/common/model/tokenizationTextModelPart.ts index 811c24b72561e..f51bf1b98bcb1 100644 --- a/src/vs/editor/common/model/tokenizationTextModelPart.ts +++ b/src/vs/editor/common/model/tokenizationTextModelPart.ts @@ -25,7 +25,7 @@ import { AbstractTokens, AttachedViewHandler, AttachedViews } from './tokens.js' import { TreeSitterTokens } from './treeSitterTokens.js'; import { ITreeSitterParserService } from '../services/treeSitterParserService.js'; import { IModelContentChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelTokensChangedEvent } from '../textModelEvents.js'; -import { BackgroundTokenizationState, ITokenizationTextModelPart, ITokenizeLineWithEditResult, LineEditWithAdditionalLines } from '../tokenizationTextModelPart.js'; +import { BackgroundTokenizationState, ITokenizationTextModelPart } from '../tokenizationTextModelPart.js'; import { ContiguousMultilineTokens } from '../tokens/contiguousMultilineTokens.js'; import { ContiguousMultilineTokensBuilder } from '../tokens/contiguousMultilineTokensBuilder.js'; import { ContiguousTokensStore } from '../tokens/contiguousTokensStore.js'; @@ -202,8 +202,8 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz return this._tokens.getTokenTypeIfInsertingCharacter(lineNumber, column, character); } - public tokenizeLineWithEdit(lineNumber: number, edit: LineEditWithAdditionalLines): ITokenizeLineWithEditResult { - return this._tokens.tokenizeLineWithEdit(lineNumber, edit); + public tokenizeLinesAt(lineNumber: number, lines: string[]): LineTokens[] | null { + return this._tokens.tokenizeLinesAt(lineNumber, lines); } // #endregion @@ -648,12 +648,13 @@ class GrammarTokens extends AbstractTokens { return this._tokenizer.getTokenTypeIfInsertingCharacter(position, character); } - public tokenizeLineWithEdit(lineNumber: number, edit: LineEditWithAdditionalLines): ITokenizeLineWithEditResult { + + public tokenizeLinesAt(lineNumber: number, lines: string[]): LineTokens[] | null { if (!this._tokenizer) { - return { mainLineTokens: null, additionalLines: null }; + return null; } this.forceTokenization(lineNumber); - return this._tokenizer.tokenizeLineWithEdit(lineNumber, edit); + return this._tokenizer.tokenizeLinesAt(lineNumber, lines); } public get hasTokens(): boolean { diff --git a/src/vs/editor/common/model/tokens.ts b/src/vs/editor/common/model/tokens.ts index 0e4ed56480cfd..493cd2d0662a4 100644 --- a/src/vs/editor/common/model/tokens.ts +++ b/src/vs/editor/common/model/tokens.ts @@ -13,7 +13,7 @@ import { ILanguageIdCodec } from '../languages.js'; import { IAttachedView } from '../model.js'; import { TextModel } from './textModel.js'; import { IModelContentChangedEvent, IModelTokensChangedEvent } from '../textModelEvents.js'; -import { BackgroundTokenizationState, ITokenizeLineWithEditResult, LineEditWithAdditionalLines } from '../tokenizationTextModelPart.js'; +import { BackgroundTokenizationState } from '../tokenizationTextModelPart.js'; import { LineTokens } from '../tokens/lineTokens.js'; /** @@ -131,7 +131,7 @@ export abstract class AbstractTokens extends Disposable { public abstract getTokenTypeIfInsertingCharacter(lineNumber: number, column: number, character: string): StandardTokenType; - public abstract tokenizeLineWithEdit(lineNumber: number, edit: LineEditWithAdditionalLines): ITokenizeLineWithEditResult; + public abstract tokenizeLinesAt(lineNumber: number, lines: string[]): LineTokens[] | null; public abstract get hasTokens(): boolean; } diff --git a/src/vs/editor/common/model/treeSitterTokens.ts b/src/vs/editor/common/model/treeSitterTokens.ts index 43ab00f0f0f41..f4077388ef088 100644 --- a/src/vs/editor/common/model/treeSitterTokens.ts +++ b/src/vs/editor/common/model/treeSitterTokens.ts @@ -10,7 +10,6 @@ import { TextModel } from './textModel.js'; import { ITreeSitterParserService } from '../services/treeSitterParserService.js'; import { IModelContentChangedEvent } from '../textModelEvents.js'; import { AbstractTokens } from './tokens.js'; -import { ITokenizeLineWithEditResult, LineEditWithAdditionalLines } from '../tokenizationTextModelPart.js'; import { IDisposable, MutableDisposable } from '../../../base/common/lifecycle.js'; export class TreeSitterTokens extends AbstractTokens { @@ -95,9 +94,9 @@ export class TreeSitterTokens extends AbstractTokens { // TODO @alexr00 implement once we have custom parsing and don't just feed in the whole text model value return StandardTokenType.Other; } - public override tokenizeLineWithEdit(lineNumber: number, edit: LineEditWithAdditionalLines): ITokenizeLineWithEditResult { + public override tokenizeLinesAt(lineNumber: number, lines: string[]): LineTokens[] | null { // TODO @alexr00 understand what this is for and implement - return { mainLineTokens: null, additionalLines: null }; + return null; } public override get hasTokens(): boolean { // TODO @alexr00 once we have a token store, implement properly diff --git a/src/vs/editor/common/tokenizationTextModelPart.ts b/src/vs/editor/common/tokenizationTextModelPart.ts index 25e7569b2f9a3..6238c606552ba 100644 --- a/src/vs/editor/common/tokenizationTextModelPart.ts +++ b/src/vs/editor/common/tokenizationTextModelPart.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OffsetEdit } from './core/offsetEdit.js'; -import { OffsetRange } from './core/offsetRange.js'; import { Range } from './core/range.js'; import { StandardTokenType } from './encodedTokenAttributes.js'; import { LineTokens } from './tokens/lineTokens.js'; @@ -85,9 +83,10 @@ export interface ITokenizationTextModelPart { getTokenTypeIfInsertingCharacter(lineNumber: number, column: number, character: string): StandardTokenType; /** + * Tokens the lines as if they were inserted at [lineNumber, lineNumber). * @internal */ - tokenizeLineWithEdit(lineNumber: number, edit: LineEditWithAdditionalLines): ITokenizeLineWithEditResult; + tokenizeLinesAt(lineNumber: number, lines: string[]): LineTokens[] | null; getLanguageId(): string; getLanguageIdAtPosition(lineNumber: number, column: number): string; @@ -97,32 +96,6 @@ export interface ITokenizationTextModelPart { readonly backgroundTokenizationState: BackgroundTokenizationState; } -export class LineEditWithAdditionalLines { - public static replace(range: OffsetRange, text: string): LineEditWithAdditionalLines { - return new LineEditWithAdditionalLines( - OffsetEdit.replace(range, text), - null, - ); - } - - constructor( - /** - * The edit for the main line. - */ - readonly lineEdit: OffsetEdit, - - /** - * Full lines appended after the main line. - */ - readonly additionalLines: string[] | null, - ) { } -} - -export interface ITokenizeLineWithEditResult { - readonly mainLineTokens: LineTokens | null; - readonly additionalLines: LineTokens[] | null; -} - export const enum BackgroundTokenizationState { InProgress = 1, Completed = 2, diff --git a/src/vs/editor/common/tokens/lineTokens.ts b/src/vs/editor/common/tokens/lineTokens.ts index 7d2b07476d06d..72d94d120d406 100644 --- a/src/vs/editor/common/tokens/lineTokens.ts +++ b/src/vs/editor/common/tokens/lineTokens.ts @@ -203,6 +203,10 @@ export class LineTokens implements IViewLineTokens { return new SliceLineTokens(this, startOffset, endOffset, deltaOffset); } + public sliceZeroCopy(range: OffsetRange): IViewLineTokens { + return this.sliceAndInflate(range.start, range.endExclusive, 0); + } + /** * @pure * @param insertTokens Must be sorted by offset. diff --git a/src/vs/editor/common/tokens/tokenArray.ts b/src/vs/editor/common/tokens/tokenArray.ts index 1b6891f223dd9..bc089d1604aed 100644 --- a/src/vs/editor/common/tokens/tokenArray.ts +++ b/src/vs/editor/common/tokens/tokenArray.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { OffsetRange } from '../core/offsetRange.js'; +import { ILanguageIdCodec } from '../languages.js'; +import { LineTokens } from './lineTokens.js'; /** * This class represents a sequence of tokens. @@ -14,6 +16,14 @@ import { OffsetRange } from '../core/offsetRange.js'; * TODO: Make this class more efficient (e.g. by using a Int32Array). */ export class TokenArray { + public static fromLineTokens(lineTokens: LineTokens): TokenArray { + const tokenInfo: TokenInfo[] = []; + for (let i = 0; i < lineTokens.getCount(); i++) { + tokenInfo.push(new TokenInfo(lineTokens.getEndOffset(i) - lineTokens.getStartOffset(i), lineTokens.getMetadata(i))); + } + return TokenArray.create(tokenInfo); + } + public static create(tokenInfo: TokenInfo[]): TokenArray { return new TokenArray(tokenInfo); } @@ -22,6 +32,10 @@ export class TokenArray { private readonly _tokenInfo: TokenInfo[], ) { } + public toLineTokens(lineContent: string, decoder: ILanguageIdCodec): LineTokens { + return LineTokens.createFromTextAndMetadata(this.map((r, t) => ({ text: r.substring(lineContent), metadata: t.metadata })), decoder); + } + public forEach(cb: (range: OffsetRange, tokenInfo: TokenInfo) => void): void { let lengthSum = 0; for (const tokenInfo of this._tokenInfo) { @@ -31,6 +45,17 @@ export class TokenArray { } } + public map(cb: (range: OffsetRange, tokenInfo: TokenInfo) => T): T[] { + const result: T[] = []; + let lengthSum = 0; + for (const tokenInfo of this._tokenInfo) { + const range = new OffsetRange(lengthSum, lengthSum + tokenInfo.length); + result.push(cb(range, tokenInfo)); + lengthSum += tokenInfo.length; + } + return result; + } + public slice(range: OffsetRange): TokenArray { const result: TokenInfo[] = []; let lengthSum = 0; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts index befcccca81a28..a240f67595ddc 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts @@ -11,6 +11,7 @@ import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js import { SetMap } from '../../../../../base/common/map.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; import { ISingleEditOperation } from '../../../../common/core/editOperation.js'; +import { SingleOffsetEdit } from '../../../../common/core/offsetEdit.js'; import { OffsetRange } from '../../../../common/core/offsetRange.js'; import { Position } from '../../../../common/core/position.js'; import { Range } from '../../../../common/core/range.js'; @@ -21,7 +22,6 @@ import { ILanguageConfigurationService } from '../../../../common/languages/lang import { ITextModel } from '../../../../common/model.js'; import { fixBracketsInLine } from '../../../../common/model/bracketPairsTextModelPart/fixBrackets.js'; import { TextModelText } from '../../../../common/model/textModelText.js'; -import { LineEditWithAdditionalLines } from '../../../../common/tokenizationTextModelPart.js'; import { SnippetParser, Text } from '../../../snippet/browser/snippetParser.js'; import { getReadonlyEmptyArray } from '../utils.js'; @@ -412,17 +412,15 @@ function getDefaultRange(position: Position, model: ITextModel): Range { } function closeBrackets(text: string, position: Position, model: ITextModel, languageConfigurationService: ILanguageConfigurationService): string { - const lineStart = model.getLineContent(position.lineNumber).substring(0, position.column - 1); - const newLine = lineStart + text; + const currentLine = model.getLineContent(position.lineNumber); + const edit = SingleOffsetEdit.replace(new OffsetRange(position.column - 1, currentLine.length), text); - const edit = LineEditWithAdditionalLines.replace(OffsetRange.ofStartAndLength(position.column - 1, newLine.length - (position.column - 1)), text); - const newTokens = model.tokenization.tokenizeLineWithEdit(position.lineNumber, edit); - const slicedTokens = newTokens?.mainLineTokens?.sliceAndInflate(position.column - 1, newLine.length, 0); - if (!slicedTokens) { + const proposedLineTokens = model.tokenization.tokenizeLinesAt(position.lineNumber, [edit.apply(currentLine)]); + const textTokens = proposedLineTokens?.[0].sliceZeroCopy(edit.getRangeAfterApply()); + if (!textTokens) { return text; } - const newText = fixBracketsInLine(slicedTokens, languageConfigurationService); - - return newText; + const fixedText = fixBracketsInLine(textTokens, languageConfigurationService); + return fixedText; } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts index 8bff9b3efbaaf..01df059293938 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts @@ -18,7 +18,6 @@ import { Range } from '../../../../../common/core/range.js'; import { StringBuilder } from '../../../../../common/core/stringBuilder.js'; import { ILanguageService } from '../../../../../common/languages/language.js'; import { IModelDeltaDecoration, ITextModel, InjectedTextCursorStops, PositionAffinity } from '../../../../../common/model.js'; -import { LineEditWithAdditionalLines } from '../../../../../common/tokenizationTextModelPart.js'; import { LineTokens } from '../../../../../common/tokens/lineTokens.js'; import { LineDecoration } from '../../../../../common/viewLayout/lineDecorations.js'; import { RenderLineInput, renderViewLine } from '../../../../../common/viewLayout/viewLineRenderer.js'; @@ -63,16 +62,14 @@ export class GhostTextView extends Disposable { const extraClassName = syntaxHighlightingEnabled ? ' syntax-highlighted' : ''; const { inlineTexts, additionalLines, hiddenRange } = computeGhostTextViewData(ghostText, textModel, 'ghost-text' + extraClassName); + const currentLine = textModel.getLineContent(ghostText.lineNumber); const edit = new OffsetEdit(inlineTexts.map(t => SingleOffsetEdit.insert(t.column - 1, t.text))); - const tokens = syntaxHighlightingEnabled ? textModel.tokenization.tokenizeLineWithEdit(ghostText.lineNumber, new LineEditWithAdditionalLines( - edit, - additionalLines.map(l => l.content) - )) : undefined; + const tokens = syntaxHighlightingEnabled ? textModel.tokenization.tokenizeLinesAt(ghostText.lineNumber, [edit.apply(currentLine), ...additionalLines.map(l => l.content)]) : undefined; const newRanges = edit.getNewTextRanges(); - const inlineTextsWithTokens = inlineTexts.map((t, idx) => ({ ...t, tokens: tokens?.mainLineTokens?.getTokensInRange(newRanges[idx]) })); + const inlineTextsWithTokens = inlineTexts.map((t, idx) => ({ ...t, tokens: tokens?.[0]?.getTokensInRange(newRanges[idx]) })); const tokenizedAdditionalLines: LineData[] = additionalLines.map((l, idx) => ({ - content: tokens?.additionalLines?.[idx] ?? LineTokens.createEmpty(l.content, this._languageService.languageIdCodec), + content: tokens?.[idx + 1] ?? LineTokens.createEmpty(l.content, this._languageService.languageIdCodec), decorations: l.decorations, })); From 41a58be380da2ab2211035600ed4859bce05a0bc Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 19 Dec 2024 12:52:16 +0100 Subject: [PATCH 122/200] Bug: The cursor must be within a commenting range to add a comment. (#236578) Fixes #236559 --- src/vs/workbench/contrib/comments/browser/commentNode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index f90a81288a3f9..89418b8131826 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -457,7 +457,7 @@ export class CommentNode extends Disposable { this._register(this._reactionsActionBar); const hasReactionHandler = this.commentService.hasReactionHandler(this.owner); - this.comment.commentReactions!.filter(reaction => !!reaction.count).map(reaction => { + this.comment.commentReactions?.filter(reaction => !!reaction.count).map(reaction => { const action = new ReactionAction(`reaction.${reaction.label}`, `${reaction.label}`, reaction.hasReacted && (reaction.canEdit || hasReactionHandler) ? 'active' : '', (reaction.canEdit || hasReactionHandler), async () => { try { await this.commentService.toggleReaction(this.owner, this.resource, this.commentThread, this.comment, reaction); From 2ba0803abfb1b749c432e4dc7a8315f98cc799a7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 19 Dec 2024 13:02:00 +0100 Subject: [PATCH 123/200] multi root - allow `--remove` for removal of workspace folders (fix #204770) (#236580) --- src/vs/platform/environment/common/argv.ts | 1 + src/vs/platform/environment/node/argv.ts | 1 + .../launch/electron-main/launchMainService.ts | 1 + .../electron-main/nativeHostMainService.ts | 1 + src/vs/platform/window/common/window.ts | 4 +- .../platform/windows/electron-main/windows.ts | 1 + .../electron-main/windowsMainService.ts | 39 ++++++----- src/vs/server/node/server.cli.ts | 1 + src/vs/workbench/api/node/extHostCLIServer.ts | 7 +- src/vs/workbench/electron-sandbox/window.ts | 67 +++++++++++-------- .../host/browser/browserHostService.ts | 21 ++++-- 11 files changed, 91 insertions(+), 53 deletions(-) diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 87825ee7d2c85..e0756ae894622 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -36,6 +36,7 @@ export interface NativeParsedArgs { diff?: boolean; merge?: boolean; add?: boolean; + remove?: boolean; goto?: boolean; 'new-window'?: boolean; 'reuse-window'?: boolean; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 4ef107c79bcdf..606aa8b277fae 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -81,6 +81,7 @@ export const OPTIONS: OptionDescriptions> = { 'diff': { type: 'boolean', cat: 'o', alias: 'd', args: ['file', 'file'], description: localize('diff', "Compare two files with each other.") }, 'merge': { type: 'boolean', cat: 'o', alias: 'm', args: ['path1', 'path2', 'base', 'result'], description: localize('merge', "Perform a three-way merge by providing paths for two modified versions of a file, the common origin of both modified versions and the output file to save merge results.") }, 'add': { type: 'boolean', cat: 'o', alias: 'a', args: 'folder', description: localize('add', "Add folder(s) to the last active window.") }, + 'remove': { type: 'boolean', cat: 'o', args: 'folder', description: localize('remove', "Remove folder(s) from the last active window.") }, 'goto': { type: 'boolean', cat: 'o', alias: 'g', args: 'file:line[:character]', description: localize('goto', "Open a file at the path on the specified line and character position.") }, 'new-window': { type: 'boolean', cat: 'o', alias: 'n', description: localize('newWindow', "Force to open a new window.") }, 'reuse-window': { type: 'boolean', cat: 'o', alias: 'r', description: localize('reuseWindow', "Force to open a file or folder in an already opened window.") }, diff --git a/src/vs/platform/launch/electron-main/launchMainService.ts b/src/vs/platform/launch/electron-main/launchMainService.ts index 36020f52cfcd4..df93721b40e31 100644 --- a/src/vs/platform/launch/electron-main/launchMainService.ts +++ b/src/vs/platform/launch/electron-main/launchMainService.ts @@ -207,6 +207,7 @@ export class LaunchMainService implements ILaunchMainService { diffMode: args.diff, mergeMode: args.merge, addMode: args.add, + removeMode: args.remove, noRecentEntry: !!args['skip-add-to-recently-opened'], gotoLineMode: args.goto }); diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index c3dbf1ee3ea78..794c5aed57532 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -220,6 +220,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain diffMode: options.diffMode, mergeMode: options.mergeMode, addMode: options.addMode, + removeMode: options.removeMode, gotoLineMode: options.gotoLineMode, noRecentEntry: options.noRecentEntry, waitMarkerFileURI: options.waitMarkerFileURI, diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index d547c37bf50e4..3a03481e55a50 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -63,6 +63,7 @@ export interface IOpenWindowOptions extends IBaseOpenWindowsOptions { readonly noRecentEntry?: boolean; readonly addMode?: boolean; + readonly removeMode?: boolean; readonly diffMode?: boolean; readonly mergeMode?: boolean; @@ -71,8 +72,9 @@ export interface IOpenWindowOptions extends IBaseOpenWindowsOptions { readonly waitMarkerFileURI?: URI; } -export interface IAddFoldersRequest { +export interface IAddRemoveFoldersRequest { readonly foldersToAdd: UriComponents[]; + readonly foldersToRemove: UriComponents[]; } interface IOpenedWindow { diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index dd5148103bde4..6d14080654d4f 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -103,6 +103,7 @@ export interface IOpenConfiguration extends IBaseOpenConfiguration { readonly diffMode?: boolean; readonly mergeMode?: boolean; addMode?: boolean; + removeMode?: boolean; readonly gotoLineMode?: boolean; readonly initialStartup?: boolean; readonly noRecentEntry?: boolean; diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index d403852efcc2a..8db5f04b7e3e9 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -37,7 +37,7 @@ import product from '../../product/common/product.js'; import { IProtocolMainService } from '../../protocol/electron-main/protocol.js'; import { getRemoteAuthority } from '../../remote/common/remoteHosts.js'; import { IStateService } from '../../state/node/state.js'; -import { IAddFoldersRequest, INativeOpenFileRequest, INativeWindowConfiguration, IOpenEmptyWindowOptions, IPath, IPathsToWaitFor, isFileToOpen, isFolderToOpen, isWorkspaceToOpen, IWindowOpenable, IWindowSettings } from '../../window/common/window.js'; +import { IAddRemoveFoldersRequest, INativeOpenFileRequest, INativeWindowConfiguration, IOpenEmptyWindowOptions, IPath, IPathsToWaitFor, isFileToOpen, isFolderToOpen, isWorkspaceToOpen, IWindowOpenable, IWindowSettings } from '../../window/common/window.js'; import { CodeWindow } from './windowImpl.js'; import { IOpenConfiguration, IOpenEmptyConfiguration, IWindowsCountChangedEvent, IWindowsMainService, OpenContext, getLastFocused } from './windows.js'; import { findWindowOnExtensionDevelopmentPath, findWindowOnFile, findWindowOnWorkspaceOrFolder } from './windowsFinder.js'; @@ -287,11 +287,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic async open(openConfig: IOpenConfiguration): Promise { this.logService.trace('windowsManager#open'); - if (openConfig.addMode && (openConfig.initialStartup || !this.getLastActiveWindow())) { - openConfig.addMode = false; // Make sure addMode is only enabled if we have an active window + // Make sure addMode/removeMode is only enabled if we have an active window + if ((openConfig.addMode || openConfig.removeMode) && (openConfig.initialStartup || !this.getLastActiveWindow())) { + openConfig.addMode = false; + openConfig.removeMode = false; } const foldersToAdd: ISingleFolderWorkspacePathToOpen[] = []; + const foldersToRemove: ISingleFolderWorkspacePathToOpen[] = []; + const foldersToOpen: ISingleFolderWorkspacePathToOpen[] = []; const workspacesToOpen: IWorkspacePathToOpen[] = []; @@ -311,6 +315,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // When run with --add, take the folders that are to be opened as // folders that should be added to the currently active window. foldersToAdd.push(path); + } else if (openConfig.removeMode) { + // When run with --remove, take the folders that are to be opened as + // folders that should be removed from the currently active window. + foldersToRemove.push(path); } else { foldersToOpen.push(path); } @@ -360,7 +368,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } // Open based on config - const { windows: usedWindows, filesOpenedInWindow } = await this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyWindowsWithBackupsToRestore, openOneEmptyWindow, filesToOpen, foldersToAdd); + const { windows: usedWindows, filesOpenedInWindow } = await this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyWindowsWithBackupsToRestore, openOneEmptyWindow, filesToOpen, foldersToAdd, foldersToRemove); this.logService.trace(`windowsManager#open used window count ${usedWindows.length} (workspacesToOpen: ${workspacesToOpen.length}, foldersToOpen: ${foldersToOpen.length}, emptyToRestore: ${emptyWindowsWithBackupsToRestore.length}, openOneEmptyWindow: ${openOneEmptyWindow})`); @@ -463,7 +471,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic emptyToRestore: IEmptyWindowBackupInfo[], openOneEmptyWindow: boolean, filesToOpen: IFilesToOpen | undefined, - foldersToAdd: ISingleFolderWorkspacePathToOpen[] + foldersToAdd: ISingleFolderWorkspacePathToOpen[], + foldersToRemove: ISingleFolderWorkspacePathToOpen[] ): Promise<{ windows: ICodeWindow[]; filesOpenedInWindow: ICodeWindow | undefined }> { // Keep track of used windows and remember @@ -482,12 +491,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // Settings can decide if files/folders open in new window or not let { openFolderInNewWindow, openFilesInNewWindow } = this.shouldOpenNewWindow(openConfig); - // Handle folders to add by looking for the last active workspace (not on initial startup) - if (!openConfig.initialStartup && foldersToAdd.length > 0) { - const authority = foldersToAdd[0].remoteAuthority; + // Handle folders to add/remove by looking for the last active workspace (not on initial startup) + if (!openConfig.initialStartup && (foldersToAdd.length > 0 || foldersToRemove.length > 0)) { + const authority = foldersToAdd.at(0)?.remoteAuthority ?? foldersToRemove.at(0)?.remoteAuthority; const lastActiveWindow = this.getLastActiveWindowForAuthority(authority); if (lastActiveWindow) { - addUsedWindow(this.doAddFoldersToExistingWindow(lastActiveWindow, foldersToAdd.map(folderToAdd => folderToAdd.workspace.uri))); + addUsedWindow(this.doAddRemoveFoldersInExistingWindow(lastActiveWindow, foldersToAdd.map(folderToAdd => folderToAdd.workspace.uri), foldersToRemove.map(folderToRemove => folderToRemove.workspace.uri))); } } @@ -671,13 +680,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic windowToFocus.focus(); } - private doAddFoldersToExistingWindow(window: ICodeWindow, foldersToAdd: URI[]): ICodeWindow { - this.logService.trace('windowsManager#doAddFoldersToExistingWindow', { foldersToAdd }); + private doAddRemoveFoldersInExistingWindow(window: ICodeWindow, foldersToAdd: URI[], foldersToRemove: URI[]): ICodeWindow { + this.logService.trace('windowsManager#doAddRemoveFoldersToExistingWindow', { foldersToAdd, foldersToRemove }); window.focus(); // make sure window has focus - const request: IAddFoldersRequest = { foldersToAdd }; - window.sendWhenReady('vscode:addFolders', CancellationToken.None, request); + const request: IAddRemoveFoldersRequest = { foldersToAdd, foldersToRemove }; + window.sendWhenReady('vscode:addRemoveFolders', CancellationToken.None, request); return window; } @@ -764,10 +773,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } // Handle the case of multiple folders being opened from CLI while we are - // not in `--add` mode by creating an untitled workspace, only if: + // not in `--add` or `--remove` mode by creating an untitled workspace, only if: // - they all share the same remote authority // - there is no existing workspace to open that matches these folders - if (!openConfig.addMode && isCommandLineOrAPICall) { + if (!openConfig.addMode && !openConfig.removeMode && isCommandLineOrAPICall) { const foldersToOpen = pathsToOpen.filter(path => isSingleFolderWorkspacePathToOpen(path)); if (foldersToOpen.length > 1) { const remoteAuthority = foldersToOpen[0].remoteAuthority; diff --git a/src/vs/server/node/server.cli.ts b/src/vs/server/node/server.cli.ts index 7f20588c3bd48..0535ddd998f70 100644 --- a/src/vs/server/node/server.cli.ts +++ b/src/vs/server/node/server.cli.ts @@ -337,6 +337,7 @@ export async function main(desc: ProductDescription, args: string[]): Promise { - const { fileURIs, folderURIs, forceNewWindow, diffMode, mergeMode, addMode, forceReuseWindow, gotoLineMode, waitMarkerFilePath, remoteAuthority } = data; + const { fileURIs, folderURIs, forceNewWindow, diffMode, mergeMode, addMode, removeMode, forceReuseWindow, gotoLineMode, waitMarkerFilePath, remoteAuthority } = data; const urisToOpen: IWindowOpenable[] = []; if (Array.isArray(folderURIs)) { for (const s of folderURIs) { @@ -144,8 +145,8 @@ export class CLIServerBase { } } const waitMarkerFileURI = waitMarkerFilePath ? URI.file(waitMarkerFilePath) : undefined; - const preferNewWindow = !forceReuseWindow && !waitMarkerFileURI && !addMode; - const windowOpenArgs: IOpenWindowOptions = { forceNewWindow, diffMode, mergeMode, addMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI, remoteAuthority }; + const preferNewWindow = !forceReuseWindow && !waitMarkerFileURI && !addMode && !removeMode; + const windowOpenArgs: IOpenWindowOptions = { forceNewWindow, diffMode, mergeMode, addMode, removeMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI, remoteAuthority }; this._commands.executeCommand('_remoteCLI.windowOpen', urisToOpen, windowOpenArgs); } diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index c186b45d2ba8f..4babd90fe708c 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -14,7 +14,7 @@ import { IFileService } from '../../platform/files/common/files.js'; import { EditorResourceAccessor, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors, IResourceDiffEditorInput, IUntypedEditorInput, IEditorPane, isResourceEditorInput, IResourceMergeEditorInput } from '../common/editor.js'; import { IEditorService } from '../services/editor/common/editorService.js'; import { ITelemetryService } from '../../platform/telemetry/common/telemetry.js'; -import { WindowMinimumSize, IOpenFileRequest, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest, hasNativeTitlebar } from '../../platform/window/common/window.js'; +import { WindowMinimumSize, IOpenFileRequest, IAddRemoveFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest, hasNativeTitlebar } from '../../platform/window/common/window.js'; import { ITitleService } from '../services/title/browser/titleService.js'; import { IWorkbenchThemeService } from '../services/themes/common/workbenchThemeService.js'; import { ApplyZoomTarget, applyZoom } from '../../platform/window/electron-sandbox/window.js'; @@ -84,8 +84,9 @@ export class NativeWindow extends BaseWindow { private readonly customTitleContextMenuDisposable = this._register(new DisposableStore()); - private readonly addFoldersScheduler = this._register(new RunOnceScheduler(() => this.doAddFolders(), 100)); + private readonly addRemoveFoldersScheduler = this._register(new RunOnceScheduler(() => this.doAddRemoveFolders(), 100)); private pendingFoldersToAdd: URI[] = []; + private pendingFoldersToRemove: URI[] = []; private isDocumentedEdited = false; @@ -209,11 +210,11 @@ export class NativeWindow extends BaseWindow { // Support openFiles event for existing and new files ipcRenderer.on('vscode:openFiles', (event: unknown, request: IOpenFileRequest) => { this.onOpenFiles(request); }); - // Support addFolders event if we have a workspace opened - ipcRenderer.on('vscode:addFolders', (event: unknown, request: IAddFoldersRequest) => { this.onAddFoldersRequest(request); }); + // Support addRemoveFolders event for workspace management + ipcRenderer.on('vscode:addRemoveFolders', (event: unknown, request: IAddRemoveFoldersRequest) => this.onAddRemoveFoldersRequest(request)); // Message support - ipcRenderer.on('vscode:showInfoMessage', (event: unknown, message: string) => { this.notificationService.info(message); }); + ipcRenderer.on('vscode:showInfoMessage', (event: unknown, message: string) => this.notificationService.info(message)); // Shell Environment Issue Notifications ipcRenderer.on('vscode:showResolveShellEnvError', (event: unknown, message: string) => { @@ -788,20 +789,6 @@ export class NativeWindow extends BaseWindow { }); } - private async openTunnel(address: string, port: number): Promise { - const remoteAuthority = this.environmentService.remoteAuthority; - const addressProvider: IAddressProvider | undefined = remoteAuthority ? { - getAddress: async (): Promise => { - return (await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority)).authority; - } - } : undefined; - const tunnel = await this.tunnelService.getExistingTunnel(address, port); - if (!tunnel || (typeof tunnel === 'string')) { - return this.tunnelService.openTunnel(addressProvider, address, port); - } - return tunnel; - } - async resolveExternalUri(uri: URI, options?: OpenOptions): Promise { let queryTunnel: RemoteTunnel | string | undefined; if (options?.allowTunneling) { @@ -826,6 +813,7 @@ export class NativeWindow extends BaseWindow { } } } + if (portMappingRequest) { const tunnel = await this.openTunnel(portMappingRequest.address, portMappingRequest.port); if (tunnel && (typeof tunnel !== 'string')) { @@ -861,6 +849,22 @@ export class NativeWindow extends BaseWindow { return undefined; } + private async openTunnel(address: string, port: number): Promise { + const remoteAuthority = this.environmentService.remoteAuthority; + const addressProvider: IAddressProvider | undefined = remoteAuthority ? { + getAddress: async (): Promise => { + return (await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority)).authority; + } + } : undefined; + + const tunnel = await this.tunnelService.getExistingTunnel(address, port); + if (!tunnel || (typeof tunnel === 'string')) { + return this.tunnelService.openTunnel(addressProvider, address, port); + } + + return tunnel; + } + private setupOpenHandlers(): void { // Handle external open() calls @@ -961,27 +965,32 @@ export class NativeWindow extends BaseWindow { //#endregion - private onAddFoldersRequest(request: IAddFoldersRequest): void { + private onAddRemoveFoldersRequest(request: IAddRemoveFoldersRequest): void { // Buffer all pending requests this.pendingFoldersToAdd.push(...request.foldersToAdd.map(folder => URI.revive(folder))); + this.pendingFoldersToRemove.push(...request.foldersToRemove.map(folder => URI.revive(folder))); // Delay the adding of folders a bit to buffer in case more requests are coming - if (!this.addFoldersScheduler.isScheduled()) { - this.addFoldersScheduler.schedule(); + if (!this.addRemoveFoldersScheduler.isScheduled()) { + this.addRemoveFoldersScheduler.schedule(); } } - private doAddFolders(): void { - const foldersToAdd: IWorkspaceFolderCreationData[] = []; - - for (const folder of this.pendingFoldersToAdd) { - foldersToAdd.push(({ uri: folder })); - } + private async doAddRemoveFolders(): Promise { + const foldersToAdd: IWorkspaceFolderCreationData[] = this.pendingFoldersToAdd.map(folder => ({ uri: folder })); + const foldersToRemove = this.pendingFoldersToRemove.slice(0); this.pendingFoldersToAdd = []; + this.pendingFoldersToRemove = []; + + if (foldersToAdd.length) { + await this.workspaceEditingService.addFolders(foldersToAdd); + } - this.workspaceEditingService.addFolders(foldersToAdd); + if (foldersToRemove.length) { + await this.workspaceEditingService.removeFolders(foldersToRemove); + } } private async onOpenFiles(request: INativeOpenFileRequest): Promise { diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index afd2a3d126737..57cb6e482cfc1 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -40,6 +40,7 @@ import { coalesce } from '../../../../base/common/arrays.js'; import { mainWindow, isAuxiliaryWindow } from '../../../../base/browser/window.js'; import { isIOS, isMacintosh } from '../../../../base/common/platform.js'; import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js'; +import { URI } from '../../../../base/common/uri.js'; enum HostShutdownReason { @@ -238,7 +239,9 @@ export class BrowserHostService extends Disposable implements IHostService { private async doOpenWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise { const payload = this.preservePayload(false /* not an empty window */, options); const fileOpenables: IFileToOpen[] = []; + const foldersToAdd: IWorkspaceFolderCreationData[] = []; + const foldersToRemove: URI[] = []; for (const openable of toOpen) { openable.label = openable.label || this.getRecentLabel(openable); @@ -246,7 +249,9 @@ export class BrowserHostService extends Disposable implements IHostService { // Folder if (isFolderToOpen(openable)) { if (options?.addMode) { - foldersToAdd.push(({ uri: openable.folderUri })); + foldersToAdd.push({ uri: openable.folderUri }); + } else if (options?.removeMode) { + foldersToRemove.push(openable.folderUri); } else { this.doOpen({ folderUri: openable.folderUri }, { reuse: this.shouldReuse(options, false /* no file */), payload }); } @@ -263,11 +268,17 @@ export class BrowserHostService extends Disposable implements IHostService { } } - // Handle Folders to Add - if (foldersToAdd.length > 0) { - this.withServices(accessor => { + // Handle Folders to add or remove + if (foldersToAdd.length > 0 || foldersToRemove.length > 0) { + this.withServices(async accessor => { const workspaceEditingService: IWorkspaceEditingService = accessor.get(IWorkspaceEditingService); - workspaceEditingService.addFolders(foldersToAdd); + if (foldersToAdd.length > 0) { + await workspaceEditingService.addFolders(foldersToAdd); + } + + if (foldersToRemove.length > 0) { + await workspaceEditingService.removeFolders(foldersToRemove); + } }); } From 225d1ca870a984369bde1a7fcd75f863fc69fee1 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 19 Dec 2024 13:07:00 +0100 Subject: [PATCH 124/200] Improved value formatting in observable logging (#236579) * Improved value formatting in observable logging * substr -> substring --- src/vs/base/common/observableInternal/logging.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vs/base/common/observableInternal/logging.ts b/src/vs/base/common/observableInternal/logging.ts index 0c343b548e47d..a6a5bb78a0698 100644 --- a/src/vs/base/common/observableInternal/logging.ts +++ b/src/vs/base/common/observableInternal/logging.ts @@ -356,6 +356,14 @@ function formatArray(value: unknown[], availableLen: number): string { } function formatObject(value: object, availableLen: number): string { + if (typeof value.toString === 'function' && value.toString !== Object.prototype.toString) { + const val = value.toString(); + if (val.length <= availableLen) { + return val; + } + return val.substring(0, availableLen - 3) + '...'; + } + let result = '{ '; let first = true; for (const [key, val] of Object.entries(value)) { From 9813e9a4a95b2c1c74fddf911ab09855ee6ba59c Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 19 Dec 2024 13:59:41 +0100 Subject: [PATCH 125/200] Revert "Pressing alt/opt makes the hover temporarily sticky (#236356)" This reverts commit 317d55da7b91e07131403663d8aa6f9499e3a112. --- .../hover/browser/contentHoverController.ts | 39 +++---------------- .../browser/contentHoverWidgetWrapper.ts | 26 +++---------- 2 files changed, 12 insertions(+), 53 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHoverController.ts b/src/vs/editor/contrib/hover/browser/contentHoverController.ts index 09cb6efcab476..11ddb36fa8381 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverController.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverController.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { DECREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID, SHOW_OR_FOCUS_HOVER_ACTION_ID } from './hoverActionIds.js'; -import { IKeyboardEvent, StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; -import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; +import { IKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; +import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; import { ICodeEditor, IEditorMouseEvent, IPartialEditorMouseEvent } from '../../../browser/editorBrowser.js'; import { ConfigurationChangedEvent, EditorOption } from '../../../common/config/editorOptions.js'; import { Range } from '../../../common/core/range.js'; @@ -22,9 +22,6 @@ import { ContentHoverWidgetWrapper } from './contentHoverWidgetWrapper.js'; import './hover.css'; import { Emitter } from '../../../../base/common/event.js'; import { isOnColorDecorator } from '../../colorPicker/browser/hoverColorPicker/hoverColorPicker.js'; -import { KeyCode } from '../../../../base/common/keyCodes.js'; -import { EventType } from '../../../../base/browser/dom.js'; -import { mainWindow } from '../../../../base/browser/window.js'; // sticky hover widget which doesn't disappear on focus out and such const _sticky = false @@ -95,18 +92,11 @@ export class ContentHoverController extends Disposable implements IEditorContrib this._listenersStore.add(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); this._listenersStore.add(this._editor.onMouseUp(() => this._onEditorMouseUp())); this._listenersStore.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e))); + this._listenersStore.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e))); this._listenersStore.add(this._editor.onMouseLeave((e) => this._onEditorMouseLeave(e))); this._listenersStore.add(this._editor.onDidChangeModel(() => this._cancelSchedulerAndHide())); this._listenersStore.add(this._editor.onDidChangeModelContent(() => this._cancelScheduler())); this._listenersStore.add(this._editor.onDidScrollChange((e: IScrollEvent) => this._onEditorScrollChanged(e))); - const keyDownListener = (e: KeyboardEvent) => this._onKeyDown(e); - const keyUpListener = (e: KeyboardEvent) => this._onKeyUp(e); - mainWindow.addEventListener(EventType.KEY_DOWN, keyDownListener); - mainWindow.addEventListener(EventType.KEY_UP, keyUpListener); - this._listenersStore.add(toDisposable(() => { - mainWindow.removeEventListener(EventType.KEY_DOWN, keyDownListener); - mainWindow.removeEventListener(EventType.KEY_UP, keyUpListener); - })); } private _unhookListeners(): void { @@ -165,9 +155,6 @@ export class ContentHoverController extends Disposable implements IEditorContrib if (_sticky) { return; } - if (this._contentWidget) { - this._contentWidget.temporarilySticky = false; - } this.hideContentHover(); } @@ -247,15 +234,11 @@ export class ContentHoverController extends Disposable implements IEditorContrib this.hideContentHover(); } - private _onKeyDown(e: KeyboardEvent): void { - if (!this._contentWidget) { + private _onKeyDown(e: IKeyboardEvent): void { + if (!this._editor.hasModel()) { return; } - const event = new StandardKeyboardEvent(e); - if (event.keyCode === KeyCode.Alt) { - this._contentWidget.temporarilySticky = true; - } - const isPotentialKeyboardShortcut = this._isPotentialKeyboardShortcut(event); + const isPotentialKeyboardShortcut = this._isPotentialKeyboardShortcut(e); if (isPotentialKeyboardShortcut) { return; } @@ -265,16 +248,6 @@ export class ContentHoverController extends Disposable implements IEditorContrib this.hideContentHover(); } - private _onKeyUp(e: KeyboardEvent): void { - if (!this._contentWidget) { - return; - } - const event = new StandardKeyboardEvent(e); - if (event.keyCode === KeyCode.Alt) { - this._contentWidget.temporarilySticky = false; - } - } - private _isPotentialKeyboardShortcut(e: IKeyboardEvent): boolean { if (!this._editor.hasModel() || !this._contentWidget) { return false; diff --git a/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts b/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts index 228e9533d299d..ad9a992096707 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts @@ -27,7 +27,6 @@ export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidge private _currentResult: ContentHoverResult | null = null; private _renderedContentHover: RenderedContentHover | undefined; - private _temporarilySticky: boolean = false; private readonly _contentHoverWidget: ContentHoverWidget; private readonly _participants: IEditorHoverParticipant[]; @@ -163,25 +162,11 @@ export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidge if (currentHoverResultIsEmpty) { currentHoverResult = null; } - const hoverVisible = this._contentHoverWidget.isVisible; - if (!hoverVisible) { - this._renderResult(currentHoverResult); - } else { - if (this._temporarilySticky) { - return; - } else { - this._renderResult(currentHoverResult); - } - } - } - - private _renderResult(currentHoverResult: ContentHoverResult | null): void { this._currentResult = currentHoverResult; if (this._currentResult) { this._showHover(this._currentResult); } else { - this._contentHoverWidget.hide(); - this._participants.forEach(participant => participant.handleHide?.()); + this._hideHover(); } } @@ -230,6 +215,11 @@ export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidge } } + private _hideHover(): void { + this._contentHoverWidget.hide(); + this._participants.forEach(participant => participant.handleHide?.()); + } + private _getHoverContext(): IEditorHoverContext { const hide = () => { this.hide(); @@ -303,10 +293,6 @@ export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidge } } - public set temporarilySticky(value: boolean) { - this._temporarilySticky = value; - } - public startShowingAtRange(range: Range, mode: HoverStartMode, source: HoverStartSource, focus: boolean): void { this._startShowingOrUpdateHover(new HoverRangeAnchor(0, range, undefined, undefined), mode, source, focus, null); } From aea3ab47f5d820198b0f68d34b3846b483f9e809 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 19 Dec 2024 14:07:44 +0100 Subject: [PATCH 126/200] Fix compilation errors --- .../editor/contrib/hover/browser/contentHoverController.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHoverController.ts b/src/vs/editor/contrib/hover/browser/contentHoverController.ts index 11ddb36fa8381..acbabe6a83e81 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverController.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverController.ts @@ -22,6 +22,7 @@ import { ContentHoverWidgetWrapper } from './contentHoverWidgetWrapper.js'; import './hover.css'; import { Emitter } from '../../../../base/common/event.js'; import { isOnColorDecorator } from '../../colorPicker/browser/hoverColorPicker/hoverColorPicker.js'; +import { KeyCode } from '../../../../base/common/keyCodes.js'; // sticky hover widget which doesn't disappear on focus out and such const _sticky = false @@ -235,14 +236,14 @@ export class ContentHoverController extends Disposable implements IEditorContrib } private _onKeyDown(e: IKeyboardEvent): void { - if (!this._editor.hasModel()) { + if (!this._contentWidget) { return; } const isPotentialKeyboardShortcut = this._isPotentialKeyboardShortcut(e); if (isPotentialKeyboardShortcut) { return; } - if (this._contentWidget.isFocused && event.keyCode === KeyCode.Tab) { + if (this._contentWidget.isFocused && e.keyCode === KeyCode.Tab) { return; } this.hideContentHover(); From 5385e31b3f7883a5524deec90f1df5faa49c4287 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 19 Dec 2024 14:10:37 +0100 Subject: [PATCH 127/200] Trigger inline edits on paste (#236584) --- src/vs/editor/browser/observableCodeEditor.ts | 13 ++++++++++++- .../controller/inlineCompletionsController.ts | 6 ++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/observableCodeEditor.ts b/src/vs/editor/browser/observableCodeEditor.ts index d8607ae319aae..3633d4cac45ca 100644 --- a/src/vs/editor/browser/observableCodeEditor.ts +++ b/src/vs/editor/browser/observableCodeEditor.ts @@ -14,7 +14,7 @@ import { Selection } from '../common/core/selection.js'; import { ICursorSelectionChangedEvent } from '../common/cursorEvents.js'; import { IModelDeltaDecoration, ITextModel } from '../common/model.js'; import { IModelContentChangedEvent } from '../common/textModelEvents.js'; -import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IOverlayWidget, IOverlayWidgetPosition } from './editorBrowser.js'; +import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IOverlayWidget, IOverlayWidgetPosition, IPasteEvent } from './editorBrowser.js'; import { Point } from './point.js'; /** @@ -94,6 +94,16 @@ export class ObservableCodeEditor extends Disposable { } })); + this._register(this.editor.onDidPaste((e) => { + this._beginUpdate(); + try { + this._forceUpdate(); + this.onDidPaste.trigger(this._currentTransaction, e); + } finally { + this._endUpdate(); + } + })); + this._register(this.editor.onDidChangeModelContent(e => { this._beginUpdate(); try { @@ -213,6 +223,7 @@ export class ObservableCodeEditor extends Disposable { public readonly cursorLineNumber = derived(this, reader => this.cursorPosition.read(reader)?.lineNumber ?? null); public readonly onDidType = observableSignal(this); + public readonly onDidPaste = observableSignal(this); public readonly scrollTop = observableFromEvent(this.editor.onDidScrollChange, () => this.editor.getScrollTop()); public readonly scrollLeft = observableFromEvent(this.editor.onDidScrollChange, () => this.editor.getScrollLeft()); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index 7c7e449571978..79fdfb85afdee 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -140,6 +140,12 @@ export class InlineCompletionsController extends Disposable { } })); + this._register(runOnChange(this._editorObs.onDidPaste, (_value, _changes) => { + if (this._enabled.get()) { + this.model.get()?.trigger(); + } + })); + this._register(this._commandService.onDidExecuteCommand((e) => { // These commands don't trigger onDidType. const commands = new Set([ From 05519998aeb60a492478948e6e196586a24c04c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 19 Dec 2024 05:10:51 -0800 Subject: [PATCH 128/200] Ignore bg terminals for confirmOnExit Fixes #235575 --- .../contrib/terminal/browser/terminalService.ts | 10 +++++++--- .../contrib/terminal/common/terminalConfiguration.ts | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 70d2aa007ec2f..698559d3b2ce9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -95,6 +95,10 @@ export class TerminalService extends Disposable implements ITerminalService { get instances(): ITerminalInstance[] { return this._terminalGroupService.instances.concat(this._terminalEditorService.instances).concat(this._backgroundedTerminalInstances); } + /** Gets all non-background terminals. */ + get foregroundInstances(): ITerminalInstance[] { + return this._terminalGroupService.instances.concat(this._terminalEditorService.instances); + } get detachedInstances(): Iterable { return this._detachedXterms; } @@ -417,7 +421,6 @@ export class TerminalService extends Disposable implements ITerminalService { if (instance.target !== TerminalLocation.Editor && instance.hasChildProcesses && (this._terminalConfigurationService.config.confirmOnKill === 'panel' || this._terminalConfigurationService.config.confirmOnKill === 'always')) { - const veto = await this._showTerminalCloseConfirmation(true); if (veto) { return; @@ -904,10 +907,11 @@ export class TerminalService extends Disposable implements ITerminalService { protected async _showTerminalCloseConfirmation(singleTerminal?: boolean): Promise { let message: string; - if (this.instances.length === 1 || singleTerminal) { + const foregroundInstances = this.foregroundInstances; + if (foregroundInstances.length === 1 || singleTerminal) { message = nls.localize('terminalService.terminalCloseConfirmationSingular', "Do you want to terminate the active terminal session?"); } else { - message = nls.localize('terminalService.terminalCloseConfirmationPlural', "Do you want to terminate the {0} active terminal sessions?", this.instances.length); + message = nls.localize('terminalService.terminalCloseConfirmationPlural', "Do you want to terminate the {0} active terminal sessions?", foregroundInstances.length); } const { confirmed } = await this._dialogService.confirm({ type: 'warning', diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index beea86681fbc3..b90a67cbbc634 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -366,7 +366,7 @@ const terminalConfiguration: IConfigurationNode = { default: 'never' }, [TerminalSettingId.ConfirmOnKill]: { - description: localize('terminal.integrated.confirmOnKill', "Controls whether to confirm killing terminals when they have child processes. When set to editor, terminals in the editor area will be marked as changed when they have child processes. Note that child process detection may not work well for shells like Git Bash which don't run their processes as child processes of the shell."), + description: localize('terminal.integrated.confirmOnKill', "Controls whether to confirm killing terminals when they have child processes. When set to editor, terminals in the editor area will be marked as changed when they have child processes. Note that child process detection may not work well for shells like Git Bash which don't run their processes as child processes of the shell. Background terminals like those launched by some extensions will not trigger the confirmation."), type: 'string', enum: ['never', 'editor', 'panel', 'always'], enumDescriptions: [ From 4051adf0a86f9ec6543be6b27840614c816a7323 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:10:05 +0100 Subject: [PATCH 129/200] Fix TypeError for undefined isShowingFilterResults (#236590) fix #236521 --- src/vs/workbench/contrib/files/browser/views/explorerView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index e77296ffed938..1c7188375010e 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -964,7 +964,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { } hasPhantomElements(): boolean { - return this.findProvider.isShowingFilterResults(); + return !!this.findProvider?.isShowingFilterResults(); } override dispose(): void { From 25b88b7e4a8f1a96487896ca8b5fe8aa045af54e Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:46:46 +0100 Subject: [PATCH 130/200] Git - fix encoding issue with stage selected ranges (#236484) --- extensions/git/src/git.ts | 4 ++-- extensions/git/src/repository.ts | 14 +++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index f2b9100d79a14..d6a6bd52cfc33 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1657,9 +1657,9 @@ export class Repository { await this.exec(args); } - async stage(path: string, data: string): Promise { + async stage(path: string, data: string, encoding: string): Promise { const child = this.stream(['hash-object', '--stdin', '-w', '--path', sanitizePath(path)], { stdio: [null, null, null] }); - child.stdin!.end(data, 'utf8'); + child.stdin!.end(iconv.encode(data, encoding)); const { exitCode, stdout } = await exec(child); const hash = stdout.toString('utf8'); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index ac2649242f2fd..0c4b50cd5e5cd 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -7,6 +7,7 @@ import TelemetryReporter from '@vscode/extension-telemetry'; import * as fs from 'fs'; import * as path from 'path'; import picomatch from 'picomatch'; +import * as iconv from '@vscode/iconv-lite-umd'; import { CancellationError, CancellationToken, CancellationTokenSource, Command, commands, Disposable, Event, EventEmitter, FileDecoration, l10n, LogLevel, LogOutputChannel, Memento, ProgressLocation, ProgressOptions, QuickDiffProvider, RelativePattern, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, TabInputNotebookDiff, TabInputTextDiff, TabInputTextMultiDiff, ThemeColor, Uri, window, workspace, WorkspaceEdit } from 'vscode'; import { ActionButton } from './actionButton'; import { ApiRepository } from './api/api1'; @@ -24,6 +25,7 @@ import { StatusBarCommands } from './statusbar'; import { toGitUri } from './uri'; import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, onceEvent, pathEquals, relativePath } from './util'; import { IFileWatcher, watch } from './watch'; +import { detectEncoding } from './encoding'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -1215,7 +1217,17 @@ export class Repository implements Disposable { async stage(resource: Uri, contents: string): Promise { const path = relativePath(this.repository.root, resource.fsPath).replace(/\\/g, '/'); await this.run(Operation.Stage, async () => { - await this.repository.stage(path, contents); + const configFiles = workspace.getConfiguration('files', Uri.file(resource.fsPath)); + let encoding = configFiles.get('encoding') ?? 'utf8'; + const autoGuessEncoding = configFiles.get('autoGuessEncoding') === true; + const candidateGuessEncodings = configFiles.get('candidateGuessEncodings') ?? []; + + if (autoGuessEncoding) { + encoding = detectEncoding(Buffer.from(contents), candidateGuessEncodings) ?? encoding; + } + + encoding = iconv.encodingExists(encoding) ? encoding : 'utf8'; + await this.repository.stage(path, contents, encoding); this._onDidChangeOriginalResource.fire(resource); this.closeDiffEditors([], [...resource.fsPath]); From 986871f71ae46e0b7b27362f1456986abc7a1fbe Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 19 Dec 2024 16:20:19 +0100 Subject: [PATCH 131/200] Proper fix for #236537 (#236596) - Support installEveryWhere option by installing the extension only on servers on which it is not installed --- .../browser/extensionsWorkbenchService.ts | 47 ++++++++++++++--- .../contrib/extensions/common/extensions.ts | 1 + .../common/extensionManagement.ts | 3 +- .../common/extensionManagementService.ts | 50 +++++++++++++++---- .../test/browser/workbenchTestServices.ts | 3 +- 5 files changed, 84 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 0967e7d34c7fc..33692adf1485d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -2342,35 +2342,69 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } else { let installableInfo: IExtensionInfo | undefined; let gallery: IGalleryExtension | undefined; + + // Install by id if (isString(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 }; } - } else if (arg.gallery) { + } + // Install by gallery + else if (arg.gallery) { extension = arg; gallery = arg.gallery; if (installOptions.version && installOptions.version !== gallery?.version) { installableInfo = { id: extension.identifier.id, version: installOptions.version }; } - } else if (arg.resourceExtension) { + } + // Install by resource + 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); } + 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()); } + if (extension?.isMalicious) { throw new Error(nls.localize('malicious', "This extension is reported to be problematic.")); } - // TODO: @sandy081 - Install the extension only on servers where it is not installed - // Do not install if requested to enable and extension is already installed - if (installOptions.installEverywhere || !(installOptions.enable && extension?.local)) { + + if (gallery) { + // If requested to install everywhere + // then install the extension in all the servers where it is not installed + if (installOptions.installEverywhere) { + installOptions.servers = []; + const installableServers = await this.extensionManagementService.getInstallableServers(gallery); + for (const extensionsServer of this.extensionsServers) { + if (installableServers.includes(extensionsServer.server) && !extensionsServer.local.find(e => areSameExtensions(e.identifier, gallery.identifier))) { + installOptions.servers.push(extensionsServer.server); + } + } + } + // If requested to enable and extension is already installed + // Check if the extension is disabled because of extension kind + // If so, install the extension in the server that is compatible. + else if (installOptions.enable && extension?.local) { + installOptions.servers = []; + if (extension.enablementState === EnablementState.DisabledByExtensionKind) { + const [installableServer] = await this.extensionManagementService.getInstallableServers(gallery); + if (installableServer) { + installOptions.servers.push(installableServer); + } + } + } + } + + if (!installOptions.servers || installOptions.servers.length) { if (!installable) { if (!gallery) { const id = isString(arg) ? arg : (arg).identifier.id; @@ -2734,8 +2768,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private installFromGallery(extension: IExtension, gallery: IGalleryExtension, installOptions?: InstallExtensionOptions): Promise { installOptions = installOptions ?? {}; installOptions.pinned = extension.local?.pinned || !this.shouldAutoUpdateExtension(extension); - // TODO: @sandy081 - Install the extension only on servers where it is not installed - if (!installOptions.installEverywhere && extension.local) { + if (extension.local && !installOptions.servers) { installOptions.productVersion = this.getProductVersion(); installOptions.operation = InstallOperation.Update; return this.extensionManagementService.updateFromGallery(gallery, extension.local, installOptions); diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 4cd9d5ea1d4e2..e8f26f634cabc 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -112,6 +112,7 @@ export interface InstallExtensionOptions extends IWorkbenchInstallOptions { version?: string; justification?: string | { reason: string; action: string }; enable?: boolean; + installEverywhere?: boolean; } export interface IExtensionsNotification { diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts index 2bc9bc6e7938e..1deee908d89b7 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts @@ -60,7 +60,7 @@ export type DidUninstallExtensionOnServerEvent = DidUninstallExtensionEvent & { export type DidChangeProfileForServerEvent = DidChangeProfileEvent & { server: IExtensionManagementServer }; export interface IWorkbenchInstallOptions extends InstallOptions { - readonly installEverywhere?: boolean; + servers?: IExtensionManagementServer[]; } export const IWorkbenchExtensionManagementService = refineServiceDecorator(IProfileAwareExtensionManagementService); @@ -84,6 +84,7 @@ export interface IWorkbenchExtensionManagementService extends IProfileAwareExten canInstall(extension: IGalleryExtension | IResourceExtension): Promise; + getInstallableServers(extension: IGalleryExtension): Promise; installVSIX(location: URI, manifest: IExtensionManifest, installOptions?: InstallOptions): Promise; installFromGallery(gallery: IGalleryExtension, installOptions?: IWorkbenchInstallOptions): Promise; installFromLocation(location: URI): Promise; diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index bc0a4687128c2..7bc18909d7f14 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -554,6 +554,14 @@ export class ExtensionManagementService extends Disposable implements IWorkbench } } + async getInstallableServers(gallery: IGalleryExtension): 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 this.getInstallableExtensionManagementServers(manifest); + } + private async uninstallExtensionFromWorkspace(extension: ILocalExtension): Promise { if (!extension.isWorkspaceScoped) { throw new Error('The extension is not a workspace extension'); @@ -606,11 +614,25 @@ export class ExtensionManagementService extends Disposable implements IWorkbench const servers: IExtensionManagementServer[] = []; - // Install everywhere if asked to install everywhere or if the extension is a language pack - if (installOptions?.installEverywhere || isLanguagePackExtension(manifest)) { + if (installOptions?.servers?.length) { + const installableServers = this.getInstallableExtensionManagementServers(manifest); + servers.push(...installOptions.servers); + for (const server of servers) { + if (!installableServers.includes(server)) { + const error = new Error(localize('cannot be installed in server', "Cannot install the '{0}' extension because it is not available in the '{1}' setup.", gallery.displayName || gallery.name, server.label)); + error.name = ExtensionManagementErrorCode.Unsupported; + throw error; + } + } + } + + // Language packs should be installed on both local and remote servers + else if (isLanguagePackExtension(manifest)) { servers.push(...this.servers.filter(server => server !== this.extensionManagementServerService.webExtensionManagementServer)); - } else { - const server = this.getExtensionManagementServerToInstall(manifest); + } + + else { + const [server] = this.getInstallableExtensionManagementServers(manifest); if (server) { servers.push(server); } @@ -633,27 +655,33 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return servers; } - private getExtensionManagementServerToInstall(manifest: IExtensionManifest): IExtensionManagementServer | null { + private getInstallableExtensionManagementServers(manifest: IExtensionManifest): IExtensionManagementServer[] { // Only local server if (this.servers.length === 1 && this.extensionManagementServerService.localExtensionManagementServer) { - return this.extensionManagementServerService.localExtensionManagementServer; + return [this.extensionManagementServerService.localExtensionManagementServer]; } + const servers: IExtensionManagementServer[] = []; + const extensionKind = this.extensionManifestPropertiesService.getExtensionKind(manifest); for (const kind of extensionKind) { if (kind === 'ui' && this.extensionManagementServerService.localExtensionManagementServer) { - return this.extensionManagementServerService.localExtensionManagementServer; + servers.push(this.extensionManagementServerService.localExtensionManagementServer); } if (kind === 'workspace' && this.extensionManagementServerService.remoteExtensionManagementServer) { - return this.extensionManagementServerService.remoteExtensionManagementServer; + servers.push(this.extensionManagementServerService.remoteExtensionManagementServer); } if (kind === 'web' && this.extensionManagementServerService.webExtensionManagementServer) { - return this.extensionManagementServerService.webExtensionManagementServer; + servers.push(this.extensionManagementServerService.webExtensionManagementServer); } } - // Local server can accept any extension. So return local server if not compatible server found. - return this.extensionManagementServerService.localExtensionManagementServer; + // Local server can accept any extension. + if (this.extensionManagementServerService.localExtensionManagementServer && !servers.includes(this.extensionManagementServerService.localExtensionManagementServer)) { + servers.push(this.extensionManagementServerService.localExtensionManagementServer); + } + + return servers; } private isExtensionsSyncEnabled(): boolean { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index a914f4586d4c4..259de7b24b0be 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -160,7 +160,7 @@ import { ILayoutOffsetInfo } from '../../../platform/layout/browser/layoutServic import { IUserDataProfile, IUserDataProfilesService, toUserDataProfile, UserDataProfilesService } from '../../../platform/userDataProfile/common/userDataProfile.js'; import { UserDataProfileService } from '../../services/userDataProfile/common/userDataProfileService.js'; import { IUserDataProfileService } from '../../services/userDataProfile/common/userDataProfile.js'; -import { EnablementState, IResourceExtension, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../services/extensionManagement/common/extensionManagement.js'; +import { EnablementState, IExtensionManagementServer, IResourceExtension, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../services/extensionManagement/common/extensionManagement.js'; import { ILocalExtension, IGalleryExtension, InstallOptions, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant, Metadata, InstallExtensionResult, InstallExtensionInfo, UninstallExtensionInfo } from '../../../platform/extensionManagement/common/extensionManagement.js'; import { Codicon } from '../../../base/common/codicons.js'; import { IRemoteExtensionsScannerService } from '../../../platform/remote/common/remoteExtensionsScanner.js'; @@ -2246,6 +2246,7 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens installResourceExtension(): Promise { throw new Error('Method not implemented.'); } getExtensions(): Promise { throw new Error('Method not implemented.'); } resetPinnedStateForAllUserExtensions(pinned: boolean): Promise { throw new Error('Method not implemented.'); } + getInstallableServers(extension: IGalleryExtension): Promise { throw new Error('Method not implemented.'); } } export class TestUserDataProfileService implements IUserDataProfileService { From f990dfb385d69e035810e3bd3102d301dd2157b7 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:27:08 +0100 Subject: [PATCH 132/200] Git - expose `env` through the extension API (#236598) --- extensions/git/src/api/api1.ts | 11 ++++++++++- extensions/git/src/git.ts | 5 +++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 8cc0b99f11329..e559c0cb8073e 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -72,7 +72,6 @@ export class ApiRepositoryUIState implements RepositoryUIState { } export class ApiRepository implements Repository { - #repository: BaseRepository; readonly rootUri: Uri; @@ -311,9 +310,19 @@ export class ApiRepository implements Repository { export class ApiGit implements Git { #model: Model; + private _env: { [key: string]: string } | undefined; + constructor(model: Model) { this.#model = model; } get path(): string { return this.#model.git.path; } + + get env(): { [key: string]: string } { + if (this._env === undefined) { + this._env = Object.freeze(this.#model.git.env); + } + + return this._env; + } } export class ApiImpl implements API { diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index d6a6bd52cfc33..87ea23e3a8538 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -315,7 +315,7 @@ export interface IGitOptions { gitPath: string; userAgent: string; version: string; - env?: any; + env?: { [key: string]: string }; } function getGitErrorCode(stderr: string): string | undefined { @@ -369,7 +369,8 @@ export class Git { readonly path: string; readonly userAgent: string; readonly version: string; - private env: any; + readonly env: { [key: string]: string }; + private commandsToLog: string[] = []; private _onOutput = new EventEmitter(); From ffbf5a7f28058787dc6476ad436b550412fce981 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:51:06 +0100 Subject: [PATCH 133/200] Use indices for queue processing instead of Array.shift() (#236601) * Use indices instead of Array.shift() * :lipstick: --- src/vs/base/browser/ui/tree/abstractTree.ts | 8 ++++---- src/vs/base/browser/ui/tree/asyncDataTree.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index b8144e58c23f3..09ce418bda94c 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -3080,16 +3080,16 @@ export abstract class AbstractTree implements IDisposable } const root = this.model.getNode(); - const queue = [root]; + const stack = [root]; - while (queue.length > 0) { - const node = queue.shift()!; + while (stack.length > 0) { + const node = stack.pop()!; if (node !== root && node.collapsible) { state.expanded[getId(node.element)] = node.collapsed ? 0 : 1; } - insertInto(queue, queue.length, node.children); + insertInto(stack, stack.length, node.children); } return state; diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 9d0d2cbf4b25f..dd6059051f084 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -24,7 +24,7 @@ import { isIterable } from '../../../common/types.js'; import { CancellationToken, CancellationTokenSource } from '../../../common/cancellation.js'; import { IContextViewProvider } from '../contextview/contextview.js'; import { FuzzyScore } from '../../../common/filters.js'; -import { splice } from '../../../common/arrays.js'; +import { insertInto, splice } from '../../../common/arrays.js'; import { localize } from '../../../../nls.js'; interface IAsyncDataTreeNode { @@ -1350,7 +1350,7 @@ export class AsyncDataTree implements IDisposable expanded.push(getId(node.element!.element as T)); } - stack.push(...node.children); + insertInto(stack, stack.length, node.children); } return { focus, selection, expanded, scrollTop: this.scrollTop }; From 613ad56601c62ff8fea04a62444217470fbc496d Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 19 Dec 2024 17:03:58 +0100 Subject: [PATCH 134/200] code cleanup (#236595) * code cleanup * Fix CI --- .../view/inlineEdits/gutterIndicatorView.ts | 82 +++++++++++-------- .../browser/view/inlineEdits/utils.ts | 11 ++- 2 files changed, 53 insertions(+), 40 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts index 936ab8b56b3be..bcfa7d7463b7d 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts @@ -40,21 +40,36 @@ export const inlineEditIndicatorBackground = registerColor( export class InlineEditsGutterIndicator extends Disposable { - private readonly _state = derived(reader => { - const range = mapOutFalsy(this._originalRange).read(reader); - if (!range) { - return undefined; - } + constructor( + private readonly _editorObs: ObservableCodeEditor, + private readonly _originalRange: IObservable, + private readonly _model: IObservable, + ) { + super(); + + this._register(this._editorObs.createOverlayWidget({ + domNode: this._indicator.element, + position: constObservable(null), + allowEditorOverflow: false, + minContentWidthInPx: constObservable(0), + })); + } + + private readonly _originalRangeObs = mapOutFalsy(this._originalRange); + private readonly _state = derived(reader => { + const range = this._originalRangeObs.read(reader); + if (!range) { return undefined; } return { range, lineOffsetRange: this._editorObs.observeLineOffsetRange(range, this._store), }; }); - private _stickyScrollController = StickyScrollController.get(this._editorObs.editor); - private readonly _stickyScrollHeight = this._stickyScrollController ? observableFromEvent(this._stickyScrollController.onDidChangeStickyScrollHeight, () => this._stickyScrollController!.stickyScrollWidgetHeight) : constObservable(0); - + private readonly _stickyScrollController = StickyScrollController.get(this._editorObs.editor); + private readonly _stickyScrollHeight = this._stickyScrollController + ? observableFromEvent(this._stickyScrollController.onDidChangeStickyScrollHeight, () => this._stickyScrollController!.stickyScrollWidgetHeight) + : constObservable(0); private readonly _layout = derived(reader => { const s = this._state.read(reader); @@ -85,13 +100,13 @@ export class InlineEditsGutterIndicator extends Disposable { return { rect, iconRect, - mode: (iconRect.top === targetRect.top ? 'right' as const + arrowDirection: (iconRect.top === targetRect.top ? 'right' as const : iconRect.top > targetRect.top ? 'top' as const : 'bottom' as const), docked: rect.containsRect(iconRect) && viewPortWithStickyScroll.containsRect(iconRect), }; }); - private readonly _mode = derived(this, reader => { + private readonly _tabAction = derived(this, reader => { const m = this._model.read(reader); if (m && m.tabShouldAcceptInlineEdit.read(reader)) { return 'accept' as const; } if (m && m.tabShouldJumpToInlineEdit.read(reader)) { return 'jump' as const; } @@ -135,16 +150,20 @@ export class InlineEditsGutterIndicator extends Disposable { cursor: 'pointer', zIndex: '1000', position: 'absolute', - backgroundColor: this._mode.map(v => ({ - inactive: 'var(--vscode-inlineEdit-gutterIndicator-secondaryBackground)', - jump: 'var(--vscode-inlineEdit-gutterIndicator-primaryBackground)', - accept: 'var(--vscode-inlineEdit-gutterIndicator-successfulBackground)', - }[v])), - '--vscodeIconForeground': this._mode.map(v => ({ - inactive: 'var(--vscode-inlineEdit-gutterIndicator-secondaryForeground)', - jump: 'var(--vscode-inlineEdit-gutterIndicator-primaryForeground)', - accept: 'var(--vscode-inlineEdit-gutterIndicator-successfulForeground)', - }[v])), + backgroundColor: this._tabAction.map(v => { + switch (v) { + case 'inactive': return 'var(--vscode-inlineEdit-gutterIndicator-secondaryBackground)'; + case 'jump': return 'var(--vscode-inlineEdit-gutterIndicator-primaryBackground)'; + case 'accept': return 'var(--vscode-inlineEdit-gutterIndicator-successfulBackground)'; + } + }), + '--vscodeIconForeground': this._tabAction.map(v => { + switch (v) { + case 'inactive': return 'var(--vscode-inlineEdit-gutterIndicator-secondaryForeground)'; + case 'jump': return 'var(--vscode-inlineEdit-gutterIndicator-primaryForeground)'; + case 'accept': return 'var(--vscode-inlineEdit-gutterIndicator-successfulForeground)'; + } + }), borderRadius: '4px', display: 'flex', justifyContent: 'center', @@ -154,7 +173,13 @@ export class InlineEditsGutterIndicator extends Disposable { }, [ n.div({ style: { - rotate: l.map(l => ({ right: '0deg', bottom: '90deg', top: '-90deg' }[l.mode])), + rotate: l.map(l => { + switch (l.arrowDirection) { + case 'right': return '0deg'; + case 'bottom': return '90deg'; + case 'top': return '-90deg'; + } + }), transition: 'rotate 0.2s ease-in-out', } }, [ @@ -162,21 +187,6 @@ export class InlineEditsGutterIndicator extends Disposable { ]) ]), ])).keepUpdated(this._store); - - constructor( - private readonly _editorObs: ObservableCodeEditor, - private readonly _originalRange: IObservable, - private readonly _model: IObservable, - ) { - super(); - - this._register(this._editorObs.createOverlayWidget({ - domNode: this._indicator.element, - position: constObservable(null), - allowEditorOverflow: false, - minContentWidthInPx: constObservable(0), - })); - } } function rectToProps(fn: (reader: IReader) => Rect): any { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts index 73b9cbb561865..b99b0478bdcde 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts @@ -423,20 +423,23 @@ type ElementAttributeKeys = Partial<{ ? never : T[K] extends object ? ElementAttributeKeys - : Value + : Value }>; -export function mapOutFalsy(obs: IObservable): IObservable | undefined | null | false> { +type RemoveFalsy = T extends false | undefined | null ? never : T; +type Falsy = T extends false | undefined | null ? T : never; + +export function mapOutFalsy(obs: IObservable): IObservable> | Falsy> { const nonUndefinedObs = derivedObservableWithCache(undefined, (reader, lastValue) => obs.read(reader) || lastValue); return derived(reader => { nonUndefinedObs.read(reader); const val = obs.read(reader); if (!val) { - return undefined; + return undefined as Falsy; } - return nonUndefinedObs as IObservable; + return nonUndefinedObs as IObservable>; }); } From 9945f3b953f1c330bc4a9b1dd02b17304d56eead Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 19 Dec 2024 10:55:15 -0600 Subject: [PATCH 135/200] Revert "focus debug console when it becomes visible " (#236607) --- src/vs/workbench/contrib/debug/browser/repl.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index a9959c5d1c53e..edef7e72b7066 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -214,7 +214,6 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { this.tree?.updateChildren(undefined, true, false); this.onDidStyleChange(); } - this.focus(); } })); this._register(this.configurationService.onDidChangeConfiguration(e => { From ceab34bc36825ebd163e625426c2d6518c177a0b Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 Dec 2024 18:10:29 +0100 Subject: [PATCH 136/200] icon themes: remove fontCharacterRegex (#236610) --- src/vs/platform/theme/common/iconRegistry.ts | 4 +--- .../services/themes/browser/fileIconThemeData.ts | 4 ++-- .../services/themes/browser/productIconThemeData.ts | 6 +++--- .../services/themes/common/fileIconThemeSchema.ts | 6 ++---- .../services/themes/common/iconExtensionPoint.ts | 10 ++-------- 5 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/vs/platform/theme/common/iconRegistry.ts b/src/vs/platform/theme/common/iconRegistry.ts index 775c1cf1d4a49..88cee9c3ddbe9 100644 --- a/src/vs/platform/theme/common/iconRegistry.ts +++ b/src/vs/platform/theme/common/iconRegistry.ts @@ -152,10 +152,8 @@ export const fontStyleRegex = /^(normal|italic|(oblique[ \w\s-]+))$/; export const fontWeightRegex = /^(normal|bold|lighter|bolder|(\d{0-1000}))$/; export const fontSizeRegex = /^([\w_.%+-]+)$/; export const fontFormatRegex = /^woff|woff2|truetype|opentype|embedded-opentype|svg$/; -export const fontCharacterRegex = /^([^\\]|\\[a-fA-F0-9]+)$/u; export const fontColorRegex = /^#[0-9a-fA-F]{0,6}$/; -export const fontCharacterErrorMessage = localize('schema.fontCharacter.formatError', 'The fontCharacter must be a single letter or a backslash followed by unicode code points in hexadecimal.'); export const fontIdErrorMessage = localize('schema.fontId.formatError', 'The font ID must only contain letters, numbers, underscores and dashes.'); class IconRegistry implements IIconRegistry { @@ -170,7 +168,7 @@ class IconRegistry implements IIconRegistry { type: 'object', properties: { fontId: { type: 'string', description: localize('iconDefinition.fontId', 'The id of the font to use. If not set, the font that is defined first is used.'), pattern: fontIdRegex.source, patternErrorMessage: fontIdErrorMessage }, - fontCharacter: { type: 'string', description: localize('iconDefinition.fontCharacter', 'The font character associated with the icon definition.'), pattern: fontCharacterRegex.source, patternErrorMessage: fontCharacterErrorMessage } + fontCharacter: { type: 'string', description: localize('iconDefinition.fontCharacter', 'The font character associated with the icon definition.') } }, additionalProperties: false, defaultSnippets: [{ body: { fontCharacter: '\\\\e030' } }] diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index e1d349be48131..a6e4c965f4957 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -13,7 +13,7 @@ import { getParseErrorMessage } from '../../../../base/common/jsonErrorMessages. import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IExtensionResourceLoaderService } from '../../../../platform/extensionResourceLoader/common/extensionResourceLoader.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; -import { fontCharacterRegex, fontColorRegex, fontSizeRegex } from '../../../../platform/theme/common/iconRegistry.js'; +import { fontColorRegex, fontSizeRegex } from '../../../../platform/theme/common/iconRegistry.js'; import * as css from '../../../../base/browser/cssValue.js'; import { fileIconSelectorEscape } from '../../../../editor/common/services/getIconClasses.js'; @@ -424,7 +424,7 @@ export class FileIconThemeLoader { if (definition.fontColor && definition.fontColor.match(fontColorRegex)) { body.push(css.inline`color: ${css.hexColorValue(definition.fontColor)};`); } - if (definition.fontCharacter && definition.fontCharacter.match(fontCharacterRegex)) { + if (definition.fontCharacter) { body.push(css.inline`content: ${css.stringValue(definition.fontCharacter)};`); } const fontSize = definition.fontSize ?? (definition.fontId ? fontSizes.get(definition.fontId) : undefined); diff --git a/src/vs/workbench/services/themes/browser/productIconThemeData.ts b/src/vs/workbench/services/themes/browser/productIconThemeData.ts index 1e0a9fa295cbc..824a35ca9e9bb 100644 --- a/src/vs/workbench/services/themes/browser/productIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/productIconThemeData.ts @@ -13,7 +13,7 @@ import { getParseErrorMessage } from '../../../../base/common/jsonErrorMessages. import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { isObject, isString } from '../../../../base/common/types.js'; import { ILogService } from '../../../../platform/log/common/log.js'; -import { IconDefinition, getIconRegistry, IconContribution, IconFontDefinition, IconFontSource, fontIdRegex, fontWeightRegex, fontStyleRegex, fontFormatRegex, fontCharacterRegex, fontCharacterErrorMessage } from '../../../../platform/theme/common/iconRegistry.js'; +import { IconDefinition, getIconRegistry, IconContribution, IconFontDefinition, IconFontSource, fontIdRegex, fontWeightRegex, fontStyleRegex, fontFormatRegex } from '../../../../platform/theme/common/iconRegistry.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { IExtensionResourceLoaderService } from '../../../../platform/extensionResourceLoader/common/extensionResourceLoader.js'; @@ -244,7 +244,7 @@ function _loadProductIconThemeDocument(fileService: IExtensionResourceLoaderServ for (const iconId in contentValue.iconDefinitions) { const definition = contentValue.iconDefinitions[iconId]; - if (isString(definition.fontCharacter) && definition.fontCharacter.match(fontCharacterRegex)) { + if (isString(definition.fontCharacter)) { const fontId = definition.fontId ?? primaryFontId; const fontDefinition = sanitizedFonts.get(fontId); if (fontDefinition) { @@ -255,7 +255,7 @@ function _loadProductIconThemeDocument(fileService: IExtensionResourceLoaderServ warnings.push(nls.localize('error.icon.font', 'Skipping icon definition \'{0}\'. Unknown font.', iconId)); } } else { - warnings.push(nls.localize('error.icon.fontCharacter', 'Skipping icon definition \'{0}\': {1}', iconId, fontCharacterErrorMessage)); + warnings.push(nls.localize('error.icon.fontCharacter', 'Skipping icon definition \'{0}\': Needs to be defined', iconId)); } } return { iconDefinitions }; diff --git a/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts b/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts index f51191117c53e..6a8ece75403b3 100644 --- a/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts @@ -7,7 +7,7 @@ import * as nls from '../../../../nls.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from '../../../../platform/jsonschemas/common/jsonContributionRegistry.js'; import { IJSONSchema } from '../../../../base/common/jsonSchema.js'; -import { fontWeightRegex, fontStyleRegex, fontSizeRegex, fontIdRegex, fontCharacterRegex, fontColorRegex, fontCharacterErrorMessage, fontIdErrorMessage } from '../../../../platform/theme/common/iconRegistry.js'; +import { fontWeightRegex, fontStyleRegex, fontSizeRegex, fontIdRegex, fontColorRegex, fontIdErrorMessage } from '../../../../platform/theme/common/iconRegistry.js'; const schemaId = 'vscode://schemas/icon-theme'; const schema: IJSONSchema = { @@ -208,9 +208,7 @@ const schema: IJSONSchema = { }, fontCharacter: { type: 'string', - description: nls.localize('schema.fontCharacter', 'When using a glyph font: The character in the font to use.'), - pattern: fontCharacterRegex.source, - patternErrorMessage: fontCharacterErrorMessage + description: nls.localize('schema.fontCharacter', 'When using a glyph font: The character in the font to use.') }, fontColor: { type: 'string', diff --git a/src/vs/workbench/services/themes/common/iconExtensionPoint.ts b/src/vs/workbench/services/themes/common/iconExtensionPoint.ts index 9b554226c3f34..08a9b0591139a 100644 --- a/src/vs/workbench/services/themes/common/iconExtensionPoint.ts +++ b/src/vs/workbench/services/themes/common/iconExtensionPoint.ts @@ -5,7 +5,7 @@ import * as nls from '../../../../nls.js'; import { ExtensionsRegistry } from '../../extensions/common/extensionsRegistry.js'; -import { IIconRegistry, Extensions as IconRegistryExtensions, fontCharacterErrorMessage, fontCharacterRegex } from '../../../../platform/theme/common/iconRegistry.js'; +import { IIconRegistry, Extensions as IconRegistryExtensions } from '../../../../platform/theme/common/iconRegistry.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import * as resources from '../../../../base/common/resources.js'; @@ -53,9 +53,7 @@ const iconConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint Date: Thu, 19 Dec 2024 19:15:05 +0100 Subject: [PATCH 137/200] Implements experimental word replacement and insertion views. (#236618) --- src/vs/editor/browser/rect.ts | 3 +- .../diffEditorViewZones/renderLines.ts | 33 +- src/vs/editor/common/config/editorOptions.ts | 19 ++ src/vs/editor/common/core/range.ts | 4 + .../defaultLinesDiffComputer.ts | 19 +- .../heuristicSequenceOptimizations.ts | 18 +- .../linesSliceCharSequence.ts | 29 ++ .../common/diff/documentDiffProvider.ts | 2 + .../editor/common/diff/linesDiffComputer.ts | 1 + .../view/inlineEdits/gutterIndicatorView.ts | 15 +- .../browser/view/inlineEdits/utils.ts | 14 + .../browser/view/inlineEdits/view.ts | 69 +++-- .../view/inlineEdits/viewAndDiffProducer.ts | 1 + .../view/inlineEdits/wordReplacementView.ts | 285 ++++++++++++++++++ src/vs/monaco.d.ts | 3 + 15 files changed, 467 insertions(+), 48 deletions(-) create mode 100644 src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/wordReplacementView.ts diff --git a/src/vs/editor/browser/rect.ts b/src/vs/editor/browser/rect.ts index b990944eb6762..6de464d201afd 100644 --- a/src/vs/editor/browser/rect.ts +++ b/src/vs/editor/browser/rect.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { BugIndicatingError } from '../../base/common/errors.js'; import { OffsetRange } from '../common/core/offsetRange.js'; import { Point } from './point.js'; @@ -49,7 +50,7 @@ export class Rect { public readonly bottom: number, ) { if (left > right || top > bottom) { - throw new Error('Invalid arguments'); + throw new BugIndicatingError('Invalid arguments'); } } diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.ts index d2ff0bf9fb8d7..ce0eea0bcaaaa 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.ts @@ -17,7 +17,7 @@ import { InlineDecoration, ViewLineRenderingData } from '../../../../../common/v const ttPolicy = createTrustedTypesPolicy('diffEditorWidget', { createHTML: value => value }); -export function renderLines(source: LineSource, options: RenderOptions, decorations: InlineDecoration[], domNode: HTMLElement): RenderLinesResult { +export function renderLines(source: LineSource, options: RenderOptions, decorations: InlineDecoration[], domNode: HTMLElement, noExtra = false): RenderLinesResult { applyFontInfo(domNode, options.fontInfo); const hasCharChanges = (decorations.length > 0); @@ -44,7 +44,8 @@ export function renderLines(source: LineSource, options: RenderOptions, decorati source.mightContainNonBasicASCII, source.mightContainRTL, options, - sb + sb, + noExtra, )); renderedLineCount++; lastBreakOffset = breakOffset; @@ -61,6 +62,7 @@ export function renderLines(source: LineSource, options: RenderOptions, decorati source.mightContainRTL, options, sb, + noExtra, )); renderedLineCount++; } @@ -125,7 +127,25 @@ export class RenderOptions { public readonly renderWhitespace: FindComputedEditorOptionValueById, public readonly renderControlCharacters: boolean, public readonly fontLigatures: FindComputedEditorOptionValueById, + public readonly setWidth = true, ) { } + + public withSetWidth(setWidth: boolean): RenderOptions { + return new RenderOptions( + this.tabSize, + this.fontInfo, + this.disableMonospaceOptimizations, + this.typicalHalfwidthCharacterWidth, + this.scrollBeyondLastColumn, + this.lineHeight, + this.lineDecorationsWidth, + this.stopRenderingLineAfter, + this.renderWhitespace, + this.renderControlCharacters, + this.fontLigatures, + setWidth, + ); + } } export interface RenderLinesResult { @@ -143,16 +163,21 @@ function renderOriginalLine( mightContainRTL: boolean, options: RenderOptions, sb: StringBuilder, + noExtra: boolean, ): number { sb.appendString('
'); + if (options.setWidth) { + sb.appendString('px;width:1000000px;">'); + } else { + sb.appendString('px;">'); + } const lineContent = lineTokens.getLineContent(); const isBasicASCII = ViewLineRenderingData.isBasicASCII(lineContent, mightContainNonBasicASCII); diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index c7481985f2f76..e355a960db99d 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -4197,6 +4197,9 @@ export interface IInlineSuggestOptions { enabled?: boolean; useMixedLinesDiff?: 'never' | 'whenPossible' | 'forStableInsertions' | 'afterJumpWhenPossible'; useInterleavedLinesDiff?: 'never' | 'always' | 'afterJump'; + useWordInsertionView?: 'never' | 'whenPossible'; + useWordReplacementView?: 'never' | 'whenPossible'; + onlyShowWhenCloseToCursor?: boolean; useGutterIndicator?: boolean; }; @@ -4230,6 +4233,8 @@ class InlineEditorSuggest extends BaseEditorOption seq.findWordContaining(idx)); if (check) { SequenceDiff.assertSorted(diffs); } + + if (options.extendToSubwords) { + diffs = extendDiffsToEntireWordIfAppropriate(slice1, slice2, diffs, (seq, idx) => seq.findSubWordContaining(idx), true); + if (check) { SequenceDiff.assertSorted(diffs); } + } + diffs = removeShortMatches(slice1, slice2, diffs); if (check) { SequenceDiff.assertSorted(diffs); } diffs = removeVeryShortMatchingTextBetweenLongDiffs(slice1, slice2, diffs); diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts index c205457ac5344..20de8dd1ab0e0 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts @@ -219,7 +219,13 @@ export function removeShortMatches(sequence1: ISequence, sequence2: ISequence, s return result; } -export function extendDiffsToEntireWordIfAppropriate(sequence1: LinesSliceCharSequence, sequence2: LinesSliceCharSequence, sequenceDiffs: SequenceDiff[]): SequenceDiff[] { +export function extendDiffsToEntireWordIfAppropriate( + sequence1: LinesSliceCharSequence, + sequence2: LinesSliceCharSequence, + sequenceDiffs: SequenceDiff[], + findParent: (seq: LinesSliceCharSequence, idx: number) => OffsetRange | undefined, + force: boolean = false, +): SequenceDiff[] { const equalMappings = SequenceDiff.invert(sequenceDiffs, sequence1.length); const additional: SequenceDiff[] = []; @@ -231,8 +237,8 @@ export function extendDiffsToEntireWordIfAppropriate(sequence1: LinesSliceCharSe return; } - const w1 = sequence1.findWordContaining(pair.offset1); - const w2 = sequence2.findWordContaining(pair.offset2); + const w1 = findParent(sequence1, pair.offset1); + const w2 = findParent(sequence2, pair.offset2); if (!w1 || !w2) { return; } @@ -252,8 +258,8 @@ export function extendDiffsToEntireWordIfAppropriate(sequence1: LinesSliceCharSe break; } - const v1 = sequence1.findWordContaining(next.seq1Range.start); - const v2 = sequence2.findWordContaining(next.seq2Range.start); + const v1 = findParent(sequence1, next.seq1Range.start); + const v2 = findParent(sequence2, next.seq2Range.start); // Because there is an intersection, we know that the words are not empty. const v = new SequenceDiff(v1!, v2!); const equalPart = v.intersect(next)!; @@ -271,7 +277,7 @@ export function extendDiffsToEntireWordIfAppropriate(sequence1: LinesSliceCharSe } } - if (equalChars1 + equalChars2 < (w.seq1Range.length + w.seq2Range.length) * 2 / 3) { + if ((force && equalChars1 + equalChars2 < w.seq1Range.length + w.seq2Range.length) || equalChars1 + equalChars2 < (w.seq1Range.length + w.seq2Range.length) * 2 / 3) { additional.push(w); } diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts index b56245bbc0f02..25b4a6127f8ce 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts @@ -144,6 +144,31 @@ export class LinesSliceCharSequence implements ISequence { return new OffsetRange(start, end); } + /** fooBar has the two sub-words foo and bar */ + public findSubWordContaining(offset: number): OffsetRange | undefined { + if (offset < 0 || offset >= this.elements.length) { + return undefined; + } + + if (!isWordChar(this.elements[offset])) { + return undefined; + } + + // find start + let start = offset; + while (start > 0 && isWordChar(this.elements[start - 1]) && !isUpperCase(this.elements[start])) { + start--; + } + + // find end + let end = offset; + while (end < this.elements.length && isWordChar(this.elements[end]) && !isUpperCase(this.elements[end])) { + end++; + } + + return new OffsetRange(start, end); + } + public countLinesIn(range: OffsetRange): number { return this.translateOffset(range.endExclusive).lineNumber - this.translateOffset(range.start).lineNumber; } @@ -165,6 +190,10 @@ function isWordChar(charCode: number): boolean { || charCode >= CharCode.Digit0 && charCode <= CharCode.Digit9; } +function isUpperCase(charCode: number): boolean { + return charCode >= CharCode.A && charCode <= CharCode.Z; +} + const enum CharBoundaryCategory { WordLower, WordUpper, diff --git a/src/vs/editor/common/diff/documentDiffProvider.ts b/src/vs/editor/common/diff/documentDiffProvider.ts index da88be843cbef..6f6f06a9d7388 100644 --- a/src/vs/editor/common/diff/documentDiffProvider.ts +++ b/src/vs/editor/common/diff/documentDiffProvider.ts @@ -45,6 +45,8 @@ export interface IDocumentDiffProviderOptions { * If set, the diff computation should compute moves in addition to insertions and deletions. */ computeMoves: boolean; + + extendToSubwords?: boolean; } /** diff --git a/src/vs/editor/common/diff/linesDiffComputer.ts b/src/vs/editor/common/diff/linesDiffComputer.ts index 054d1eebfdd63..6a384b6d8a565 100644 --- a/src/vs/editor/common/diff/linesDiffComputer.ts +++ b/src/vs/editor/common/diff/linesDiffComputer.ts @@ -13,6 +13,7 @@ export interface ILinesDiffComputerOptions { readonly ignoreTrimWhitespace: boolean; readonly maxComputationTimeMs: number; readonly computeMoves: boolean; + readonly extendToSubwords?: boolean; } export class LinesDiff { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts index bcfa7d7463b7d..b0f8b3dc54dc5 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts @@ -6,7 +6,7 @@ import { renderIcon } from '../../../../../../base/browser/ui/iconLabel/iconLabels.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import { IObservable, IReader, constObservable, derived, observableFromEvent } from '../../../../../../base/common/observable.js'; +import { IObservable, constObservable, derived, observableFromEvent } from '../../../../../../base/common/observable.js'; import { buttonBackground, buttonForeground, buttonSecondaryBackground, buttonSecondaryForeground } from '../../../../../../platform/theme/common/colorRegistry.js'; import { registerColor, transparent } from '../../../../../../platform/theme/common/colorUtils.js'; import { ObservableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; @@ -16,7 +16,7 @@ import { LineRange } from '../../../../../common/core/lineRange.js'; import { OffsetRange } from '../../../../../common/core/offsetRange.js'; import { StickyScrollController } from '../../../../stickyScroll/browser/stickyScrollController.js'; import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js'; -import { mapOutFalsy, n } from './utils.js'; +import { mapOutFalsy, n, rectToProps } from './utils.js'; export const inlineEditIndicatorPrimaryForeground = registerColor('inlineEdit.gutterIndicator.primaryForeground', buttonForeground, 'Foreground color for the primary inline edit gutter indicator.'); export const inlineEditIndicatorPrimaryBackground = registerColor('inlineEdit.gutterIndicator.primaryBackground', buttonBackground, 'Background color for the primary inline edit gutter indicator.'); @@ -157,7 +157,7 @@ export class InlineEditsGutterIndicator extends Disposable { case 'accept': return 'var(--vscode-inlineEdit-gutterIndicator-successfulBackground)'; } }), - '--vscodeIconForeground': this._tabAction.map(v => { + ['--vscodeIconForeground' as any]: this._tabAction.map(v => { switch (v) { case 'inactive': return 'var(--vscode-inlineEdit-gutterIndicator-secondaryForeground)'; case 'jump': return 'var(--vscode-inlineEdit-gutterIndicator-primaryForeground)'; @@ -188,12 +188,3 @@ export class InlineEditsGutterIndicator extends Disposable { ]), ])).keepUpdated(this._store); } - -function rectToProps(fn: (reader: IReader) => Rect): any { - return { - left: derived(reader => fn(reader).left), - top: derived(reader => fn(reader).top), - width: derived(reader => fn(reader).right - fn(reader).left), - height: derived(reader => fn(reader).bottom - fn(reader).top), - }; -} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts index b99b0478bdcde..a70a0721491ab 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts @@ -16,6 +16,7 @@ import { URI } from '../../../../../../base/common/uri.js'; import { MenuEntryActionViewItem } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { ObservableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; import { Point } from '../../../../../browser/point.js'; +import { Rect } from '../../../../../browser/rect.js'; import { EditorOption } from '../../../../../common/config/editorOptions.js'; import { LineRange } from '../../../../../common/core/lineRange.js'; import { OffsetRange } from '../../../../../common/core/offsetRange.js'; @@ -173,9 +174,13 @@ type SVGElementTagNameMap2 = { width: number; height: number; transform: string; + viewBox: string; + fill: string; }; path: SVGElement & { d: string; + stroke: string; + fill: string; }; linearGradient: SVGElement & { id: string; @@ -465,3 +470,12 @@ export function observeElementPosition(element: HTMLElement, store: DisposableSt left }; } + +export function rectToProps(fn: (reader: IReader) => Rect) { + return { + left: derived(reader => fn(reader).left), + top: derived(reader => fn(reader).top), + width: derived(reader => fn(reader).right - fn(reader).left), + height: derived(reader => fn(reader).bottom - fn(reader).top), + }; +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts index 68bc963e374a7..dfb11c8bebf21 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts @@ -4,14 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import { autorunWithStore, derived, IObservable, IReader } from '../../../../../../base/common/observable.js'; +import { autorunWithStore, derived, IObservable, IReader, mapObservableArrayCached } from '../../../../../../base/common/observable.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { ICodeEditor } from '../../../../../browser/editorBrowser.js'; import { observableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; import { EditorOption } from '../../../../../common/config/editorOptions.js'; import { LineRange } from '../../../../../common/core/lineRange.js'; import { Position } from '../../../../../common/core/position.js'; -import { StringText } from '../../../../../common/core/textEdit.js'; +import { SingleTextEdit, StringText } from '../../../../../common/core/textEdit.js'; +import { TextLength } from '../../../../../common/core/textLength.js'; import { DetailedLineRangeMapping, lineRangeMappingFromRangeMappings, RangeMapping } from '../../../../../common/diff/rangeMapping.js'; import { TextModel } from '../../../../../common/model/textModel.js'; import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js'; @@ -22,12 +23,15 @@ import { InlineEditsSideBySideDiff } from './sideBySideDiff.js'; import { applyEditToModifiedRangeMappings, createReindentEdit } from './utils.js'; import './view.css'; import { InlineEditWithChanges } from './viewAndDiffProducer.js'; +import { WordInsertView, WordReplacementView } from './wordReplacementView.js'; export class InlineEditsView extends Disposable { private readonly _editorObs = observableCodeEditor(this._editor); private readonly _useMixedLinesDiff = observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.experimental.useMixedLinesDiff); private readonly _useInterleavedLinesDiff = observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.experimental.useInterleavedLinesDiff); + private readonly _useWordReplacementView = observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.experimental.useWordReplacementView); + private readonly _useWordInsertionView = observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.experimental.useWordInsertionView); constructor( private readonly _editor: ICodeEditor, @@ -39,7 +43,7 @@ export class InlineEditsView extends Disposable { } private readonly _uiState = derived<{ - state: 'collapsed' | 'mixedLines' | 'ghostText' | 'interleavedLines' | 'sideBySide'; + state: ReturnType; diff: DetailedLineRangeMapping[]; edit: InlineEditWithChanges; newText: string; @@ -57,9 +61,9 @@ export class InlineEditsView extends Disposable { let newText = edit.edit.apply(edit.originalText); let diff = lineRangeMappingFromRangeMappings(mappings, edit.originalText, new StringText(newText)); - const state = this.determinRenderState(edit, reader, diff); + const state = this.determineRenderState(edit, reader, diff, new StringText(newText)); - if (state === 'sideBySide') { + if (state.kind === 'sideBySide') { const indentationAdjustmentEdit = createReindentEdit(newText, edit.modifiedLineRange); newText = indentationAdjustmentEdit.applyToString(newText); @@ -98,7 +102,7 @@ export class InlineEditsView extends Disposable { this._editor, this._edit, this._previewTextModel, - this._uiState.map(s => s && s.state === 'sideBySide' ? ({ + this._uiState.map(s => s && s.state.kind === 'sideBySide' ? ({ edit: s.edit, newTextLineCount: s.newTextLineCount, originalDisplayRange: s.originalDisplayRange, @@ -108,17 +112,27 @@ export class InlineEditsView extends Disposable { private readonly _inlineDiffViewState = derived(this, reader => { const e = this._uiState.read(reader); if (!e) { return undefined; } - + if (e.state.kind === 'wordReplacements') { + return undefined; + } return { modifiedText: new StringText(e.newText), diff: e.diff, - mode: e.state === 'collapsed' ? 'sideBySide' : e.state, + mode: e.state.kind === 'collapsed' ? 'sideBySide' : e.state.kind, modifiedCodeEditor: this._sideBySide.previewEditor, }; }); protected readonly _inlineDiffView = this._register(new OriginalEditorInlineDiffView(this._editor, this._inlineDiffViewState, this._previewTextModel)); + protected readonly _wordReplacementViews = mapObservableArrayCached(this, this._uiState.map(s => s?.state.kind === 'wordReplacements' ? s.state.replacements : []), (e, store) => { + if (e.range.isEmpty()) { + return store.add(this._instantiationService.createInstance(WordInsertView, this._editorObs, e)); + } else { + return store.add(this._instantiationService.createInstance(WordReplacementView, this._editorObs, e)); + } + }).recomputeInitiallyAndOnChange(this._store); + private readonly _useGutterIndicator = observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.experimental.useGutterIndicator); protected readonly _indicator = this._register(autorunWithStore((reader, store) => { @@ -136,37 +150,54 @@ export class InlineEditsView extends Disposable { if (!state) { return undefined; } const range = state.originalDisplayRange; const top = this._editor.getTopForLineNumber(range.startLineNumber) - this._editorObs.scrollTop.read(reader); - return { editTop: top, showAlways: state.state !== 'sideBySide' }; + return { editTop: top, showAlways: state.state.kind !== 'sideBySide' }; }), this._model, )); } })); - private determinRenderState(edit: InlineEditWithChanges, reader: IReader, diff: DetailedLineRangeMapping[]) { + private determineRenderState(edit: InlineEditWithChanges, reader: IReader, diff: DetailedLineRangeMapping[], newText: StringText) { if (edit.isCollapsed) { - return 'collapsed'; + return { kind: 'collapsed' as const }; } if ( - (this._useMixedLinesDiff.read(reader) === 'whenPossible' || (edit.userJumpedToIt && this._useMixedLinesDiff.read(reader) === 'afterJumpWhenPossible')) - && diff.every(m => OriginalEditorInlineDiffView.supportsInlineDiffRendering(m)) + this._useMixedLinesDiff.read(reader) === 'forStableInsertions' + && isInsertionAfterPosition(diff, edit.cursorPosition) ) { - return 'mixedLines'; + return { kind: 'ghostText' as const }; + } + + if (diff.length === 1 && diff[0].original.length === 1 && diff[0].modified.length === 1) { + const inner = diff.flatMap(d => d.innerChanges!); + if (inner.every( + m => (m.originalRange.isEmpty() && this._useWordInsertionView.read(reader) === 'whenPossible' + || !m.originalRange.isEmpty() && this._useWordReplacementView.read(reader) === 'whenPossible') + && TextLength.ofRange(m.originalRange).columnCount < 100 + && TextLength.ofRange(m.modifiedRange).columnCount < 100 + )) { + return { + kind: 'wordReplacements' as const, + replacements: inner.map(i => + new SingleTextEdit(i.originalRange, newText.getValueOfRange(i.modifiedRange)) + ) + }; + } } if ( - this._useMixedLinesDiff.read(reader) === 'forStableInsertions' - && isInsertionAfterPosition(diff, edit.cursorPosition) + (this._useMixedLinesDiff.read(reader) === 'whenPossible' || (edit.userJumpedToIt && this._useMixedLinesDiff.read(reader) === 'afterJumpWhenPossible')) + && diff.every(m => OriginalEditorInlineDiffView.supportsInlineDiffRendering(m)) ) { - return 'ghostText'; + return { kind: 'mixedLines' as const }; } if (this._useInterleavedLinesDiff.read(reader) === 'always' || (edit.userJumpedToIt && this._useInterleavedLinesDiff.read(reader) === 'afterJump')) { - return 'interleavedLines'; + return { kind: 'interleavedLines' as const }; } - return 'sideBySide'; + return { kind: 'sideBySide' as const }; } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/viewAndDiffProducer.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/viewAndDiffProducer.ts index ed40c6c0edbd5..f56b4d2c9867b 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/viewAndDiffProducer.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/viewAndDiffProducer.ts @@ -47,6 +47,7 @@ export class InlineEditsViewAndDiffProducer extends Disposable { computeMoves: false, ignoreTrimWhitespace: false, maxComputationTimeMs: 1000, + extendToSubwords: true, }, CancellationToken.None); return result; }); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/wordReplacementView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/wordReplacementView.ts new file mode 100644 index 0000000000000..6e6f213d38ae5 --- /dev/null +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/wordReplacementView.ts @@ -0,0 +1,285 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { constObservable, derived } from '../../../../../../base/common/observable.js'; +import { editorHoverStatusBarBackground } from '../../../../../../platform/theme/common/colorRegistry.js'; +import { registerColor, transparent } from '../../../../../../platform/theme/common/colorUtils.js'; +import { ObservableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; +import { Point } from '../../../../../browser/point.js'; +import { Rect } from '../../../../../browser/rect.js'; +import { LineSource, renderLines, RenderOptions } from '../../../../../browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; +import { EditorOption } from '../../../../../common/config/editorOptions.js'; +import { SingleOffsetEdit } from '../../../../../common/core/offsetEdit.js'; +import { OffsetRange } from '../../../../../common/core/offsetRange.js'; +import { SingleTextEdit } from '../../../../../common/core/textEdit.js'; +import { ILanguageService } from '../../../../../common/languages/language.js'; +import { LineTokens } from '../../../../../common/tokens/lineTokens.js'; +import { TokenArray } from '../../../../../common/tokens/tokenArray.js'; +import { mapOutFalsy, n, rectToProps } from './utils.js'; + +export const transparentHoverBackground = registerColor( + 'inlineEdit.wordReplacementView.background', + { + light: transparent(editorHoverStatusBarBackground, 0.1), + dark: transparent(editorHoverStatusBarBackground, 0.5), + hcLight: transparent(editorHoverStatusBarBackground, 0.1), + hcDark: transparent(editorHoverStatusBarBackground, 0.1), + }, + 'Background color for the inline edit word replacement view.' +); + +export class WordReplacementView extends Disposable { + private readonly _start = this._editor.observePosition(constObservable(this._edit.range.getStartPosition()), this._store); + private readonly _end = this._editor.observePosition(constObservable(this._edit.range.getEndPosition()), this._store); + + private readonly _line = document.createElement('div'); + + private readonly _text = derived(reader => { + const tm = this._editor.model.get()!; + const origLine = tm.getLineContent(this._edit.range.startLineNumber); + + const edit = SingleOffsetEdit.replace(new OffsetRange(this._edit.range.startColumn - 1, this._edit.range.endColumn - 1), this._edit.text); + const lineToTokenize = edit.apply(origLine); + const t = tm.tokenization.tokenizeLinesAt(this._edit.range.startLineNumber, [lineToTokenize])?.[0]; + let tokens: LineTokens; + if (t) { + tokens = TokenArray.fromLineTokens(t).slice(edit.getRangeAfterApply()).toLineTokens(this._edit.text, this._languageService.languageIdCodec); + } else { + tokens = LineTokens.createEmpty(this._edit.text, this._languageService.languageIdCodec); + } + renderLines(new LineSource([tokens]), RenderOptions.fromEditor(this._editor.editor).withSetWidth(false), [], this._line, true); + }); + + private readonly _layout = derived(this, reader => { + this._text.read(reader); + const start = this._start.read(reader); + const end = this._end.read(reader); + if (!start || !end) { + return undefined; + } + const contentLeft = this._editor.layoutInfoContentLeft.read(reader); + const lineHeight = this._editor.getOption(EditorOption.lineHeight).read(reader); + if (start.x > end.x) { + return undefined; + } + const original = Rect.fromLeftTopWidthHeight(start.x + contentLeft - this._editor.scrollLeft.read(reader), start.y, end.x - start.x, lineHeight); + const w = this._editor.getOption(EditorOption.fontInfo).read(reader).typicalHalfwidthCharacterWidth; + const modified = Rect.fromLeftTopWidthHeight(original.left + 20, original.top + lineHeight + 5, this._edit.text.length * w + 5, original.height); + const background = Rect.hull([original, modified]).withMargin(4); + + return { + original, + modified, + background, + lowerBackground: background.intersectVertical(new OffsetRange(original.bottom, Number.MAX_SAFE_INTEGER)), + }; + }); + + + + private readonly _div = n.div({ + class: 'word-replacement', + }, [ + derived(reader => { + const layout = mapOutFalsy(this._layout).read(reader); + if (!layout) { + return []; + } + + return [ + n.div({ + style: { + position: 'absolute', + ...rectToProps(reader => layout.read(reader).lowerBackground), + borderRadius: '4px', + background: 'var(--vscode-editor-background)' + } + }, []), + n.div({ + style: { + position: 'absolute', + ...rectToProps(reader => layout.read(reader).modified), + borderRadius: '4px', + padding: '0px', + textAlign: 'center', + background: 'var(--vscode-inlineEdit-modifiedChangedTextBackground)', + fontFamily: this._editor.getOption(EditorOption.fontFamily), + fontSize: this._editor.getOption(EditorOption.fontSize), + fontWeight: this._editor.getOption(EditorOption.fontWeight), + } + }, [ + this._line, + ]), + n.div({ + style: { + position: 'absolute', + ...rectToProps(reader => layout.read(reader).original), + borderRadius: '4px', + boxSizing: 'border-box', + background: 'var(--vscode-inlineEdit-originalChangedTextBackground)', + } + }, []), + n.div({ + style: { + position: 'absolute', + ...rectToProps(reader => layout.read(reader).background), + borderRadius: '4px', + + border: '1px solid var(--vscode-editorHoverWidget-border)', + //background: 'rgba(122, 122, 122, 0.12)', looks better + background: 'var(--vscode-inlineEdit-wordReplacementView-background)', + } + }, []), + + n.svg({ + width: 11, + height: 13, + viewBox: '0 0 11 13', + fill: 'none', + style: { + position: 'absolute', + left: derived(reader => layout.read(reader).modified.left - 15), + top: derived(reader => layout.read(reader).modified.top), + } + }, [ + n.svgElem('path', { + d: 'M1 0C1 2.98966 1 4.92087 1 7.49952C1 8.60409 1.89543 9.5 3 9.5H10.5', + stroke: 'var(--vscode-editorHoverWidget-foreground)', + }), + n.svgElem('path', { + d: 'M6 6.5L9.99999 9.49998L6 12.5', + stroke: 'var(--vscode-editorHoverWidget-foreground)', + }) + ]), + + ]; + }) + ]).keepUpdated(this._store); + + constructor( + private readonly _editor: ObservableCodeEditor, + /** Must be single-line in both sides */ + private readonly _edit: SingleTextEdit, + @ILanguageService private readonly _languageService: ILanguageService, + ) { + super(); + + this._register(this._editor.createOverlayWidget({ + domNode: this._div.element, + minContentWidthInPx: constObservable(0), + position: constObservable({ preference: { top: 0, left: 0 } }), + allowEditorOverflow: false, + })); + } +} + +export class WordInsertView extends Disposable { + private readonly _start = this._editor.observePosition(constObservable(this._edit.range.getStartPosition()), this._store); + + private readonly _layout = derived(this, reader => { + const start = this._start.read(reader); + if (!start) { + return undefined; + } + const contentLeft = this._editor.layoutInfoContentLeft.read(reader); + const lineHeight = this._editor.getOption(EditorOption.lineHeight).read(reader); + + const w = this._editor.getOption(EditorOption.fontInfo).read(reader).typicalHalfwidthCharacterWidth; + const width = this._edit.text.length * w + 5; + + const center = new Point(contentLeft + start.x + w / 2 - this._editor.scrollLeft.read(reader), start.y); + + const modified = Rect.fromLeftTopWidthHeight(center.x - width / 2, center.y + lineHeight + 5, width, lineHeight); + const background = Rect.hull([Rect.fromPoint(center), modified]).withMargin(4); + + return { + modified, + center, + background, + lowerBackground: background.intersectVertical(new OffsetRange(modified.top - 2, Number.MAX_SAFE_INTEGER)), + }; + }); + + private readonly _div = n.div({ + class: 'word-insert', + }, [ + derived(reader => { + const layout = mapOutFalsy(this._layout).read(reader); + if (!layout) { + return []; + } + + return [ + n.div({ + style: { + position: 'absolute', + ...rectToProps(reader => layout.read(reader).lowerBackground), + borderRadius: '4px', + background: 'var(--vscode-editor-background)' + } + }, []), + n.div({ + style: { + position: 'absolute', + ...rectToProps(reader => layout.read(reader).modified), + borderRadius: '4px', + padding: '0px', + textAlign: 'center', + background: 'var(--vscode-inlineEdit-modifiedChangedTextBackground)', + fontFamily: this._editor.getOption(EditorOption.fontFamily), + fontSize: this._editor.getOption(EditorOption.fontSize), + fontWeight: this._editor.getOption(EditorOption.fontWeight), + } + }, [ + this._edit.text, + ]), + n.div({ + style: { + position: 'absolute', + ...rectToProps(reader => layout.read(reader).background), + borderRadius: '4px', + border: '1px solid var(--vscode-editorHoverWidget-border)', + //background: 'rgba(122, 122, 122, 0.12)', looks better + background: 'var(--vscode-inlineEdit-wordReplacementView-background)', + } + }, []), + n.svg({ + viewBox: '0 0 12 18', + width: 12, + height: 18, + fill: 'none', + style: { + position: 'absolute', + left: derived(reader => layout.read(reader).center.x - 9), + top: derived(reader => layout.read(reader).center.y + 4), + transform: 'scale(1.4, 1.4)', + } + }, [ + n.svgElem('path', { + d: 'M5.06445 0H7.35759C7.35759 0 7.35759 8.47059 7.35759 11.1176C7.35759 13.7647 9.4552 18 13.4674 18C17.4795 18 -2.58445 18 0.281373 18C3.14719 18 5.06477 14.2941 5.06477 11.1176C5.06477 7.94118 5.06445 0 5.06445 0Z', + fill: 'var(--vscode-inlineEdit-modifiedChangedTextBackground)', + }) + ]) + + ]; + }) + ]).keepUpdated(this._store); + + constructor( + private readonly _editor: ObservableCodeEditor, + /** Must be single-line in both sides */ + private readonly _edit: SingleTextEdit, + ) { + super(); + + this._register(this._editor.createOverlayWidget({ + domNode: this._div.element, + minContentWidthInPx: constObservable(0), + position: constObservable({ preference: { top: 0, left: 0 } }), + allowEditorOverflow: false, + })); + } +} diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 7b6d78e85528c..c1df5f99ffe16 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -773,6 +773,7 @@ declare namespace monaco { * Moves the range by the given amount of lines. */ delta(lineCount: number): Range; + isSingleLine(): boolean; static fromPositions(start: IPosition, end?: IPosition): Range; /** * Create a `Range` from an `IRange`. @@ -4604,6 +4605,8 @@ declare namespace monaco.editor { enabled?: boolean; useMixedLinesDiff?: 'never' | 'whenPossible' | 'forStableInsertions' | 'afterJumpWhenPossible'; useInterleavedLinesDiff?: 'never' | 'always' | 'afterJump'; + useWordInsertionView?: 'never' | 'whenPossible'; + useWordReplacementView?: 'never' | 'whenPossible'; onlyShowWhenCloseToCursor?: boolean; useGutterIndicator?: boolean; }; From a6210a08ac0156635424b396625d52256ac188fc Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 19 Dec 2024 12:18:50 -0600 Subject: [PATCH 138/200] user aria alert vs native when navigating in terminal accessible view (#236622) fix #236621 --- .../accessibility/browser/terminal.accessibility.contribution.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index 5c69ee4bae962..b0f68a6eecfcd 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -33,6 +33,7 @@ import { BufferContentTracker } from './bufferContentTracker.js'; import { TerminalAccessibilityHelpProvider } from './terminalAccessibilityHelp.js'; import { ICommandWithEditorLine, TerminalAccessibleBufferProvider } from './terminalAccessibleBufferProvider.js'; import { TextAreaSyncAddon } from './textAreaSyncAddon.js'; +import { alert } from '../../../../../base/browser/ui/aria/aria.js'; // #region Terminal Contributions From 8be4be068e75248770be81abb5e8fc9f5d2e2302 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 19 Dec 2024 19:21:00 +0100 Subject: [PATCH 139/200] Fixes bug (#236620) --- .../browser/view/inlineEdits/gutterIndicatorView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts index b0f8b3dc54dc5..739f12b4feb27 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts @@ -108,8 +108,8 @@ export class InlineEditsGutterIndicator extends Disposable { private readonly _tabAction = derived(this, reader => { const m = this._model.read(reader); - if (m && m.tabShouldAcceptInlineEdit.read(reader)) { return 'accept' as const; } if (m && m.tabShouldJumpToInlineEdit.read(reader)) { return 'jump' as const; } + if (m && m.tabShouldAcceptInlineEdit.read(reader)) { return 'accept' as const; } return 'inactive' as const; }); From d55cb9a7a04ab9b894b3d3f51ae5a0f4589bf924 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 19 Dec 2024 10:25:37 -0800 Subject: [PATCH 140/200] Use claims to force an idToken in Broker flow (#236623) Looks like the Broker doesn't support `forceRefresh`... This is an alternative way of forcing a refresh. Fixes https://github.com/microsoft/vscode/issues/229456 --- .../src/node/cachedPublicClientApplication.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts b/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts index 7396da1799018..0f27c2c0e4d62 100644 --- a/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts +++ b/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts @@ -102,9 +102,19 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica ); if (fiveMinutesBefore < new Date()) { this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] id token is expired or about to expire. Forcing refresh...`); - result = await this._sequencer.queue(() => this._pca.acquireTokenSilent({ ...request, forceRefresh: true })); + const newRequest = this._isBrokerAvailable + // HACK: Broker doesn't support forceRefresh so we need to pass in claims which will force a refresh + ? { ...request, claims: '{ "id_token": {}}' } + : { ...request, forceRefresh: true }; + result = await this._sequencer.queue(() => this._pca.acquireTokenSilent(newRequest)); this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] got refreshed result`); } + const newIdTokenExpirationInSecs = (result.idTokenClaims as { exp?: number }).exp; + if (newIdTokenExpirationInSecs) { + if (new Date(newIdTokenExpirationInSecs * 1000) < new Date()) { + this._logger.error(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] id token is still expired.`); + } + } } // this._setupRefresh(result); From fe68aa5447827cbdc970fa14e572ab8c0b572f71 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 19 Dec 2024 12:52:06 -0600 Subject: [PATCH 141/200] fix go to symbol in accessible view (#236624) --- .../accessibility/browser/accessibleView.ts | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index c9acf7b6a28c9..fde082ed5d56e 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -78,7 +78,7 @@ export class AccessibleView extends Disposable implements ITextModelContentProvi private _hasAssignedKeybindings: IContextKey; private _codeBlocks?: ICodeBlock[]; - private _inQuickPick: boolean = false; + private _isInQuickPick: boolean = false; get editorWidget() { return this._editorWidget; } private _container: HTMLElement; @@ -352,6 +352,7 @@ export class AccessibleView extends Disposable implements ITextModelContentProvi if (!this._currentProvider) { return; } + this._isInQuickPick = true; this._instantiationService.createInstance(AccessibleViewSymbolQuickPick, this).show(this._currentProvider); } @@ -388,11 +389,11 @@ export class AccessibleView extends Disposable implements ITextModelContentProvi } getSymbols(): IAccessibleViewSymbol[] | undefined { - const provider = this._currentProvider instanceof AccessibleContentProvider ? this._currentProvider : undefined; + const provider = this._currentProvider ? this._currentProvider : undefined; if (!this._currentContent || !provider) { return; } - const symbols: IAccessibleViewSymbol[] = provider.getSymbols?.() || []; + const symbols: IAccessibleViewSymbol[] = 'getSymbols' in provider ? provider.getSymbols?.() || [] : []; if (symbols?.length) { return symbols; } @@ -416,7 +417,7 @@ export class AccessibleView extends Disposable implements ITextModelContentProvi } configureKeybindings(unassigned: boolean): void { - this._inQuickPick = true; + this._isInQuickPick = true; const provider = this._updateLastProvider(); const items = unassigned ? provider?.options?.configureKeybindingItems : provider?.options?.configuredKeybindingItems; if (!items) { @@ -440,7 +441,7 @@ export class AccessibleView extends Disposable implements ITextModelContentProvi this.show(provider); } disposables.dispose(); - this._inQuickPick = false; + this._isInQuickPick = false; })); } @@ -495,6 +496,7 @@ export class AccessibleView extends Disposable implements ITextModelContentProvi if (lineNumber === undefined) { return; } + this._isInQuickPick = false; this.show(provider, undefined, undefined, { lineNumber, column: 1 }); this._updateContextKeys(provider, true); } @@ -609,11 +611,14 @@ export class AccessibleView extends Disposable implements ITextModelContentProvi this._updateToolbar(this._currentProvider.actions, provider.options.type); const hide = (e?: KeyboardEvent | IKeyboardEvent): void => { - if (!this._inQuickPick) { + if (!this._isInQuickPick) { provider.onClose(); } e?.stopPropagation(); this._contextViewService.hideContextView(); + if (this._isInQuickPick) { + return; + } this._updateContextKeys(provider, false); this._lastProvider = undefined; this._currentContent = undefined; @@ -938,11 +943,15 @@ class AccessibleViewSymbolQuickPick { for (const symbol of symbols) { picks.push({ label: symbol.label, - ariaLabel: symbol.ariaLabel + ariaLabel: symbol.ariaLabel, + firstListItem: symbol.firstListItem, + lineNumber: symbol.lineNumber, + endLineNumber: symbol.endLineNumber, + markdownToParse: symbol.markdownToParse }); } quickPick.canSelectMany = false; - quickPick.items = symbols; + quickPick.items = picks; quickPick.show(); disposables.add(quickPick.onDidAccept(() => { this._accessibleView.showSymbol(provider, quickPick.selectedItems[0]); From d34e04970c4ad3955693708dd2c481bf7c8ccd80 Mon Sep 17 00:00:00 2001 From: RedCMD <33529441+RedCMD@users.noreply.github.com> Date: Fri, 20 Dec 2024 07:54:50 +1300 Subject: [PATCH 142/200] Add `outdated` and `recentlyUpdated` suggestions to extension filter (#235884) --- src/vs/workbench/contrib/extensions/common/extensionQuery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/common/extensionQuery.ts b/src/vs/workbench/contrib/extensions/common/extensionQuery.ts index d6cd57bb5bc90..68320b1455a83 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionQuery.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionQuery.ts @@ -12,7 +12,7 @@ export class Query { } static suggestions(query: string): string[] { - const commands = ['installed', 'updates', 'enabled', 'disabled', 'builtin', 'featured', 'popular', 'recommended', 'recentlyPublished', 'workspaceUnsupported', 'deprecated', 'sort', 'category', 'tag', 'ext', 'id'] as const; + const commands = ['installed', 'updates', 'enabled', 'disabled', 'builtin', 'featured', 'popular', 'recommended', 'recentlyPublished', 'workspaceUnsupported', 'deprecated', 'sort', 'category', 'tag', 'ext', 'id', 'outdated', 'recentlyUpdated'] as const; const subcommands = { 'sort': ['installs', 'rating', 'name', 'publishedDate', 'updateDate'], 'category': EXTENSION_CATEGORIES.map(c => `"${c.toLowerCase()}"`), From 1cbc2eedcd75198db22137ab81701d09d6ee1290 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 19 Dec 2024 13:30:17 -0600 Subject: [PATCH 143/200] rm `auto` as a type for voice synthesizer setting (#236616) fix #229403 --- .../accessibility/browser/accessibilityConfiguration.ts | 5 ++--- .../chat/electron-sandbox/actions/voiceChatActions.ts | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 32eff1e159403..fc060c2832e6e 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -806,14 +806,13 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen }, [AccessibilityVoiceSettingId.AutoSynthesize]: { 'type': 'string', - 'enum': ['on', 'off', 'auto'], + 'enum': ['on', 'off'], 'enumDescriptions': [ localize('accessibility.voice.autoSynthesize.on', "Enable the feature. When a screen reader is enabled, note that this will disable aria updates."), localize('accessibility.voice.autoSynthesize.off', "Disable the feature."), - localize('accessibility.voice.autoSynthesize.auto', "When a screen reader is detected, disable the feature. Otherwise, enable the feature.") ], 'markdownDescription': localize('autoSynthesize', "Whether a textual response should automatically be read out aloud when speech was used as input. For example in a chat session, a response is automatically synthesized when voice was used as chat request."), - 'default': this.productService.quality !== 'stable' ? 'auto' : 'off', + 'default': this.productService.quality !== 'stable' ? 'on' : 'off', 'tags': ['accessibility'] } } diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 57310027bc9eb..f3fe9985e72c4 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -369,8 +369,8 @@ class VoiceChatSessions { if (!response) { return; } - const autoSynthesize = this.configurationService.getValue<'on' | 'off' | 'auto'>(AccessibilityVoiceSettingId.AutoSynthesize); - if (autoSynthesize === 'on' || autoSynthesize === 'auto' && !this.accessibilityService.isScreenReaderOptimized()) { + const autoSynthesize = this.configurationService.getValue<'on' | 'off'>(AccessibilityVoiceSettingId.AutoSynthesize); + if (autoSynthesize === 'on' || (autoSynthesize !== 'off' && !this.accessibilityService.isScreenReaderOptimized())) { let context: IVoiceChatSessionController | 'focused'; if (controller.context === 'inline') { // This is ugly, but the lightweight inline chat turns into From 2a99d4331539247b4933b992ec768653d3634b61 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Thu, 19 Dec 2024 11:44:22 -0800 Subject: [PATCH 144/200] fix: don't watch untitled files for chat editing (#236634) --- .../chatEditing/chatEditingModifiedFileEntry.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts index 6becb0867930b..aa105b2e76730 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts @@ -6,6 +6,7 @@ import { RunOnceScheduler } from '../../../../../base/common/async.js'; import { Emitter } from '../../../../../base/common/event.js'; import { Disposable, IReference, toDisposable } from '../../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../../base/common/network.js'; import { IObservable, ITransaction, observableValue, transaction } from '../../../../../base/common/observable.js'; import { themeColorFromId } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; @@ -171,12 +172,15 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie this._register(this.doc.onDidChangeContent(e => this._mirrorEdits(e))); - this._register(this._fileService.watch(this.modifiedURI)); - this._register(this._fileService.onDidFilesChange(e => { - if (e.affects(this.modifiedURI) && kind === ChatEditKind.Created && e.gotDeleted()) { - this._onDidDelete.fire(); - } - })); + + if (this.modifiedURI.scheme !== Schemas.untitled) { + this._register(this._fileService.watch(this.modifiedURI)); + this._register(this._fileService.onDidFilesChange(e => { + if (e.affects(this.modifiedURI) && kind === ChatEditKind.Created && e.gotDeleted()) { + this._onDidDelete.fire(); + } + })); + } this._register(toDisposable(() => { this._clearCurrentEditLineDecoration(); From 2217f51cf8d7022cafe49301a523ea55cbddc5b4 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 19 Dec 2024 21:01:41 +0100 Subject: [PATCH 145/200] Fix title bar focus command when hidden (#236635) fix #236597 --- src/vs/workbench/browser/parts/titlebar/titlebarPart.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 3facaf51e236e..32dad57a21830 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -54,6 +54,7 @@ import { IBaseActionViewItemOptions } from '../../../../base/browser/ui/actionba import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js'; import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; import { safeIntl } from '../../../../base/common/date.js'; +import { TitleBarVisibleContext } from '../../../common/contextkeys.js'; export interface ITitleVariable { readonly name: string; @@ -119,11 +120,12 @@ export class BrowserTitleService extends MultiWindowParts i title: localize2('focusTitleBar', 'Focus Title Bar'), category: Categories.View, f1: true, + precondition: TitleBarVisibleContext }); } run(): void { - that.getPartByDocument(getActiveDocument()).focus(); + that.getPartByDocument(getActiveDocument())?.focus(); } })); } From e48d729d217084342cacf71bbfa6c1fa0d867feb Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 19 Dec 2024 12:09:55 -0800 Subject: [PATCH 146/200] Apply margin to action items (#236638) Fixes https://github.com/microsoft/vscode/issues/233699 --- .../platform/quickinput/browser/media/quickInput.css | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/quickinput/browser/media/quickInput.css b/src/vs/platform/quickinput/browser/media/quickInput.css index 9108ee9ae80e6..53f42dfa5ae26 100644 --- a/src/vs/platform/quickinput/browser/media/quickInput.css +++ b/src/vs/platform/quickinput/browser/media/quickInput.css @@ -26,8 +26,14 @@ flex: 1; } -.quick-input-inline-action-bar { - margin: 2px 0 0 5px; +/* give some space between input and action bar */ +.quick-input-inline-action-bar > .actions-container > .action-item:first-child { + margin-left: 5px; +} + +/* center horizontally */ +.quick-input-inline-action-bar > .actions-container > .action-item { + margin-top: 2px; } .quick-input-title { From 358e96ab1e9017d8dc90b7a24e617d9f39e017b4 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 19 Dec 2024 12:40:19 -0800 Subject: [PATCH 147/200] Cancel if the user dismisses the modal (#236642) Fixes https://github.com/microsoft/vscode/issues/235364 --- .../microsoft-authentication/src/node/authProvider.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/extensions/microsoft-authentication/src/node/authProvider.ts b/extensions/microsoft-authentication/src/node/authProvider.ts index af34273afa4d0..cc8eb2bc5c7de 100644 --- a/extensions/microsoft-authentication/src/node/authProvider.ts +++ b/extensions/microsoft-authentication/src/node/authProvider.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AccountInfo, AuthenticationResult, ServerError } from '@azure/msal-node'; +import { AccountInfo, AuthenticationResult, ClientAuthError, ClientAuthErrorCodes, ServerError } from '@azure/msal-node'; import { AuthenticationGetSessionOptions, AuthenticationProvider, AuthenticationProviderAuthenticationSessionsChangeEvent, AuthenticationProviderSessionOptions, AuthenticationSession, AuthenticationSessionAccountInformation, CancellationError, env, EventEmitter, ExtensionContext, l10n, LogOutputChannel, Uri, window } from 'vscode'; import { Environment } from '@azure/ms-rest-azure-env'; import { CachedPublicClientApplicationManager } from './publicClientCache'; @@ -229,6 +229,12 @@ export class MsalAuthProvider implements AuthenticationProvider { throw e; } + // The user closed the modal window + if ((e as ClientAuthError).errorCode === ClientAuthErrorCodes.userCanceled) { + this._telemetryReporter.sendLoginFailedEvent(); + throw e; + } + // The user wants to try the loopback client or we got an error likely due to spinning up the server const loopbackClient = new UriHandlerLoopbackClient(this._uriHandler, redirectUri, this._logger); try { From ad359b1ca9090f340655e8839a76abb9b59f8761 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Thu, 19 Dec 2024 12:58:38 -0800 Subject: [PATCH 148/200] Clear nb selection highlights on non-explicit cursor events (#236641) ugh I overthought this. clear on non-explicit cursor events. (ie typing) --- .../browser/contrib/multicursor/notebookSelectionHighlight.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/multicursor/notebookSelectionHighlight.ts b/src/vs/workbench/contrib/notebook/browser/contrib/multicursor/notebookSelectionHighlight.ts index a7e82c41a73be..305e4d8fde458 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/multicursor/notebookSelectionHighlight.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/multicursor/notebookSelectionHighlight.ts @@ -63,6 +63,7 @@ class NotebookSelectionHighlighter extends Disposable implements INotebookEditor this.anchorDisposables.clear(); this.anchorDisposables.add(this.anchorCell[1].onDidChangeCursorPosition((e) => { if (e.reason !== CursorChangeReason.Explicit) { + this.clearNotebookSelectionDecorations(); return; } From 6ec5e7b296e7d96e8fbd87d1329bf5642d738da2 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 19 Dec 2024 13:06:18 -0800 Subject: [PATCH 149/200] debug: fix tree 'measuring node not in DOM error' (#236645) --- .../workbench/contrib/debug/browser/repl.ts | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index edef7e72b7066..04823d1f137c3 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -174,7 +174,11 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { this.onDidFocusSession(this.debugService.getViewModel().focusedSession); } - this._register(this.debugService.getViewModel().onDidFocusSession(async session => this.onDidFocusSession(session))); + this._register(this.debugService.getViewModel().onDidFocusSession(session => { + if (this.isVisible()) { + 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); @@ -201,19 +205,27 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { } })); 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(); - } + if (!visible) { + return; + } + if (!this.model) { + this.model = this.modelService.getModel(Repl.URI) || this.modelService.createModel('', null, Repl.URI, true); + } + + const focusedSession = this.debugService.getViewModel().focusedSession; + if (this.tree && this.tree.getInput() !== focusedSession) { + this.onDidFocusSession(focusedSession); + } + + 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 => { From f442df17470c5b95936acc79bbca27509177cf31 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 19 Dec 2024 22:06:52 +0100 Subject: [PATCH 150/200] Git - better match git conflict decorations (#236646) --- extensions/git/src/commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index dde1c99049a87..95ab9ada07136 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -309,7 +309,7 @@ async function categorizeResourceByResolution(resources: Resource[]): Promise<{ const isBothAddedOrModified = (s: Resource) => s.type === Status.BOTH_MODIFIED || s.type === Status.BOTH_ADDED; const isAnyDeleted = (s: Resource) => s.type === Status.DELETED_BY_THEM || s.type === Status.DELETED_BY_US; const possibleUnresolved = merge.filter(isBothAddedOrModified); - const promises = possibleUnresolved.map(s => grep(s.resourceUri.fsPath, /^<{7}|^={7}|^>{7}/)); + const promises = possibleUnresolved.map(s => grep(s.resourceUri.fsPath, /^<{7}\s|^={7}$|^>{7}\s/)); const unresolvedBothModified = await Promise.all(promises); const resolved = possibleUnresolved.filter((_s, i) => !unresolvedBothModified[i]); const deletionConflicts = merge.filter(s => isAnyDeleted(s)); From ee8c3e9e8acd54968405d96dea233b31c32c6830 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 19 Dec 2024 15:52:14 -0600 Subject: [PATCH 151/200] add `Terminal: resize suggest widget size` command (#236639) fix #235091 --- .../suggest/browser/terminal.suggest.contribution.ts | 6 ++++++ .../terminalContrib/suggest/browser/terminalSuggestAddon.ts | 4 ++++ .../terminalContrib/suggest/common/terminal.suggest.ts | 1 + .../services/suggest/browser/simpleSuggestWidget.ts | 4 ++++ 4 files changed, 15 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts index 96676a5a12c13..35a845c1f7b85 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts @@ -195,6 +195,12 @@ registerActiveInstanceAction({ run: (activeInstance) => TerminalSuggestContribution.get(activeInstance)?.addon?.requestCompletions(true) }); +registerActiveInstanceAction({ + id: TerminalSuggestCommandId.ResetWidgetSize, + title: localize2('workbench.action.terminal.resetSuggestWidgetSize', 'Reset Suggest Widget Size'), + run: (activeInstance) => TerminalSuggestContribution.get(activeInstance)?.addon?.resetWidgetSize() +}); + registerActiveInstanceAction({ id: TerminalSuggestCommandId.SelectPrevSuggestion, title: localize2('workbench.action.terminal.selectPrevSuggestion', 'Select the Previous Suggestion'), diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index 9f173cfc15a68..94650c7525c00 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -231,6 +231,10 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest this._screen = screen; } + resetWidgetSize(): void { + this._suggestWidget?.resetWidgetSize(); + } + async requestCompletions(explicitlyInvoked?: boolean): Promise { if (!this._promptInputModel) { return; diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminal.suggest.ts b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminal.suggest.ts index b9fe0dcbe6082..0b62f7901242c 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminal.suggest.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminal.suggest.ts @@ -13,6 +13,7 @@ export const enum TerminalSuggestCommandId { HideSuggestWidget = 'workbench.action.terminal.hideSuggestWidget', ClearSuggestCache = 'workbench.action.terminal.clearSuggestCache', RequestCompletions = 'workbench.action.terminal.requestCompletions', + ResetWidgetSize = 'workbench.action.terminal.resetSuggestWidgetSize', } export const defaultTerminalSuggestCommandsToSkipShell = [ diff --git a/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts b/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts index bc9c9ea0cba97..af70d32b896d5 100644 --- a/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts +++ b/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts @@ -290,6 +290,10 @@ export class SimpleSuggestWidget extends Disposable { return this._completionModel?.items.length !== 0; } + resetWidgetSize(): void { + this._persistedSize.reset(); + } + showSuggestions(selectionIndex: number, isFrozen: boolean, isAuto: boolean, cursorPosition: { top: number; left: number; height: number }): void { this._cursorPosition = cursorPosition; From 41793c2d43f18397608003a7af524ce660b0b4c3 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 19 Dec 2024 16:26:26 -0600 Subject: [PATCH 152/200] use keyboard vs key icon for configure keybinding action icons (#236649) fix #229827 --- .../contrib/accessibility/browser/accessibleViewActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts index d1be576a13165..f587bd7cb1eeb 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts @@ -233,7 +233,7 @@ class AccessibilityHelpConfigureKeybindingsAction extends Action2 { super({ id: AccessibilityCommandId.AccessibilityHelpConfigureKeybindings, precondition: ContextKeyExpr.and(accessibilityHelpIsShown, accessibleViewHasUnassignedKeybindings), - icon: Codicon.key, + icon: Codicon.recordKeys, keybinding: { primary: KeyMod.Alt | KeyCode.KeyK, weight: KeybindingWeight.WorkbenchContrib @@ -260,7 +260,7 @@ class AccessibilityHelpConfigureAssignedKeybindingsAction extends Action2 { super({ id: AccessibilityCommandId.AccessibilityHelpConfigureAssignedKeybindings, precondition: ContextKeyExpr.and(accessibilityHelpIsShown, accessibleViewHasAssignedKeybindings), - icon: Codicon.key, + icon: Codicon.recordKeys, keybinding: { primary: KeyMod.Alt | KeyCode.KeyA, weight: KeybindingWeight.WorkbenchContrib From 38c3ebef2e3e9069552b835855271dcf0ac3fe4e Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 19 Dec 2024 17:39:08 -0800 Subject: [PATCH 153/200] Remove repeated call, warn on invalid persisted data (#236658) --- src/vs/workbench/contrib/chat/common/chatModel.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 14fa03dffa0f7..d1b092b114cdb 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -999,12 +999,17 @@ export class ChatModel extends Disposable implements IChatModel { ) { super(); - this._isImported = (!!initialData && !isSerializableSessionData(initialData)) || (initialData?.isImported ?? false); - this._sessionId = (isSerializableSessionData(initialData) && initialData.sessionId) || generateUuid(); + const isValid = isSerializableSessionData(initialData); + if (initialData && !isValid) { + this.logService.warn(`ChatModel#constructor: Loaded malformed session data: ${JSON.stringify(initialData)}`); + } + + this._isImported = (!!initialData && !isValid) || (initialData?.isImported ?? false); + this._sessionId = (isValid && initialData.sessionId) || generateUuid(); this._requests = initialData ? this._deserialize(initialData) : []; - this._creationDate = (isSerializableSessionData(initialData) && initialData.creationDate) || Date.now(); - this._lastMessageDate = (isSerializableSessionData(initialData) && initialData.lastMessageDate) || this._creationDate; - this._customTitle = isSerializableSessionData(initialData) ? initialData.customTitle : undefined; + this._creationDate = (isValid && initialData.creationDate) || Date.now(); + this._lastMessageDate = (isValid && initialData.lastMessageDate) || this._creationDate; + this._customTitle = isValid ? initialData.customTitle : undefined; this._initialRequesterAvatarIconUri = initialData?.requesterAvatarIconUri && URI.revive(initialData.requesterAvatarIconUri); this._initialResponderAvatarIconUri = isUriComponents(initialData?.responderAvatarIconUri) ? URI.revive(initialData.responderAvatarIconUri) : initialData?.responderAvatarIconUri; From 89f808979a5151bd91324e65d4f7ab1b62896983 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 19 Dec 2024 19:39:33 -0800 Subject: [PATCH 154/200] Cancel request tool calls when cancelling chat request (#236663) * Cancel request tool calls when cancelling chat request Fix #232775 * Don't invoke tool if cancelled before * Fix tests --- .../chat/browser/languageModelToolsService.ts | 72 +++++++++++++++++-- .../contrib/chat/common/chatServiceImpl.ts | 11 ++- .../chat/common/languageModelToolsService.ts | 1 + .../browser/languageModelToolsService.test.ts | 71 +++++++++++++++++- .../chat/test/common/mockChatService.ts | 7 +- .../common/mockLanguageModelToolsService.ts | 3 + .../test/browser/inlineChatController.test.ts | 3 + 7 files changed, 156 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 2b3f6b2dc497d..d0bce841451c2 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -5,11 +5,11 @@ import { renderStringAsPlaintext } from '../../../../base/browser/markdownRenderer.js'; import { RunOnceScheduler } from '../../../../base/common/async.js'; -import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { CancellationError, isCancellationError } from '../../../../base/common/errors.js'; import { Emitter } from '../../../../base/common/event.js'; import { Iterable } from '../../../../base/common/iterator.js'; -import { Disposable, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { localize } from '../../../../nls.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; @@ -37,6 +37,9 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo private _tools = new Map(); private _toolContextKeys = new Set(); + + private _callsByRequestId = new Map(); + constructor( @IExtensionService private readonly _extensionService: IExtensionService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -141,10 +144,34 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo // Shortcut to write to the model directly here, but could call all the way back to use the real stream. let toolInvocation: ChatToolInvocation | undefined; + let requestId: string | undefined; + let store: DisposableStore | undefined; try { if (dto.context) { - const model = this._chatService.getSession(dto.context?.sessionId) as ChatModel; + store = new DisposableStore(); + const model = this._chatService.getSession(dto.context?.sessionId) as ChatModel | undefined; + if (!model) { + throw new Error(`Tool called for unknown chat session`); + } + const request = model.getRequests().at(-1)!; + requestId = request.id; + + // Replace the token with a new token that we can cancel when cancelToolCallsForRequest is called + if (!this._callsByRequestId.has(requestId)) { + this._callsByRequestId.set(requestId, []); + } + this._callsByRequestId.get(requestId)!.push(store); + + const source = new CancellationTokenSource(); + store.add(toDisposable(() => { + toolInvocation!.confirmed.complete(false); + source.dispose(true); + })); + store.add(token.onCancellationRequested(() => { + source.cancel(); + })); + token = source.token; const prepared = tool.impl.prepareToolInvocation ? await tool.impl.prepareToolInvocation(dto.parameters, token) @@ -153,9 +180,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo const defaultMessage = localize('toolInvocationMessage', "Using {0}", `"${tool.data.displayName}"`); const invocationMessage = prepared?.invocationMessage ?? defaultMessage; toolInvocation = new ChatToolInvocation(invocationMessage, prepared?.confirmationMessages); - token.onCancellationRequested(() => { - toolInvocation!.confirmed.complete(false); - }); + model.acceptResponseProgress(request, toolInvocation); if (prepared?.confirmationMessages) { const userConfirmed = await toolInvocation.confirmed.p; @@ -176,6 +201,9 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo } } + if (token.isCancellationRequested) { + throw new CancellationError(); + } const result = await tool.impl.invoke(dto, countTokens, token); this._telemetryService.publicLog2( @@ -200,7 +228,39 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo throw err; } finally { toolInvocation?.isCompleteDeferred.complete(); + + if (requestId && store) { + this.cleanupCallDisposables(requestId, store); + } + } + } + + private cleanupCallDisposables(requestId: string, store: DisposableStore): void { + const disposables = this._callsByRequestId.get(requestId); + if (disposables) { + const index = disposables.indexOf(store); + if (index > -1) { + disposables.splice(index, 1); + } + if (disposables.length === 0) { + this._callsByRequestId.delete(requestId); + } } + store.dispose(); + } + + cancelToolCallsForRequest(requestId: string): void { + const calls = this._callsByRequestId.get(requestId); + if (calls) { + calls.forEach(call => call.dispose()); + this._callsByRequestId.delete(requestId); + } + } + + public override dispose(): void { + super.dispose(); + + this._callsByRequestId.forEach(calls => dispose(calls)); } } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index d5a8839dd11b4..6cfb29ce0c956 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -33,6 +33,7 @@ import { ChatServiceTelemetry } from './chatServiceTelemetry.js'; import { IChatSlashCommandService } from './chatSlashCommands.js'; import { IChatVariablesService } from './chatVariables.js'; import { ChatMessageRole, IChatMessage } from './languageModels.js'; +import { ILanguageModelToolsService } from './languageModelToolsService.js'; const serializedChatKey = 'interactive.sessions'; @@ -86,7 +87,8 @@ const maxPersistedSessions = 25; class CancellableRequest implements IDisposable { constructor( public readonly cancellationTokenSource: CancellationTokenSource, - public requestId?: string | undefined + public requestId: string | undefined, + @ILanguageModelToolsService private readonly toolsService: ILanguageModelToolsService ) { } dispose() { @@ -94,6 +96,10 @@ class CancellableRequest implements IDisposable { } cancel() { + if (this.requestId) { + this.toolsService.cancelToolCallsForRequest(this.requestId); + } + this.cancellationTokenSource.cancel(); } } @@ -778,7 +784,8 @@ export class ChatService extends Disposable implements IChatService { } }; const rawResponsePromise = sendRequestInternal(); - this._pendingRequests.set(model.sessionId, new CancellableRequest(source)); + // Note- requestId is not known at this point, assigned later + this._pendingRequests.set(model.sessionId, this.instantiationService.createInstance(CancellableRequest, source, undefined)); rawResponsePromise.finally(() => { this._pendingRequests.deleteAndDispose(model.sessionId); }); diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index f2251ca8166f7..f69be4fed5c68 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -86,4 +86,5 @@ export interface ILanguageModelToolsService { getTool(id: string): IToolData | undefined; getToolByName(name: string): IToolData | undefined; invokeTool(invocation: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise; + cancelToolCallsForRequest(requestId: string): void; } diff --git a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts index 9679c7a890893..fb866bbfade5e 100644 --- a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts @@ -6,24 +6,32 @@ import * as assert from 'assert'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; +import { ContextKeyService } from '../../../../../platform/contextkey/browser/contextKeyService.js'; import { ContextKeyEqualsExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; import { LanguageModelToolsService } from '../../browser/languageModelToolsService.js'; +import { IChatModel } from '../../common/chatModel.js'; +import { IChatService } from '../../common/chatService.js'; import { IToolData, IToolImpl, IToolInvocation } from '../../common/languageModelToolsService.js'; -import { ContextKeyService } from '../../../../../platform/contextkey/browser/contextKeyService.js'; -import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; +import { MockChatService } from '../common/mockChatService.js'; +import { CancellationError, isCancellationError } from '../../../../../base/common/errors.js'; +import { Barrier } from '../../../../../base/common/async.js'; suite('LanguageModelToolsService', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); let contextKeyService: IContextKeyService; let service: LanguageModelToolsService; + let chatService: MockChatService; setup(() => { const instaService = workbenchInstantiationService({ - contextKeyService: () => store.add(new ContextKeyService(new TestConfigurationService)) + contextKeyService: () => store.add(new ContextKeyService(new TestConfigurationService)), }, store); contextKeyService = instaService.get(IContextKeyService); + chatService = new MockChatService(); + instaService.stub(IChatService, chatService); service = store.add(instaService.createInstance(LanguageModelToolsService)); }); @@ -122,4 +130,61 @@ suite('LanguageModelToolsService', () => { const result = await service.invokeTool(dto, async () => 0, CancellationToken.None); assert.strictEqual(result.content[0].value, 'result'); }); + + test('cancel tool call', async () => { + const toolData: IToolData = { + id: 'testTool', + modelDescription: 'Test Tool', + displayName: 'Test Tool' + }; + + store.add(service.registerToolData(toolData)); + + const toolBarrier = new Barrier(); + const toolImpl: IToolImpl = { + invoke: async (invocation, countTokens, cancelToken) => { + assert.strictEqual(invocation.callId, '1'); + assert.strictEqual(invocation.toolId, 'testTool'); + assert.deepStrictEqual(invocation.parameters, { a: 1 }); + await toolBarrier.wait(); + if (cancelToken.isCancellationRequested) { + throw new CancellationError(); + } else { + throw new Error('Tool call should be cancelled'); + } + } + }; + + store.add(service.registerToolImplementation('testTool', toolImpl)); + + const sessionId = 'sessionId'; + const requestId = 'requestId'; + const dto: IToolInvocation = { + callId: '1', + toolId: 'testTool', + tokenBudget: 100, + parameters: { + a: 1 + }, + context: { + sessionId + }, + }; + chatService.addSession({ + sessionId: sessionId, + getRequests: () => { + return [{ + id: requestId + }]; + }, + acceptResponseProgress: () => { } + } as any as IChatModel); + + const toolPromise = service.invokeTool(dto, async () => 0, CancellationToken.None); + service.cancelToolCallsForRequest(requestId); + toolBarrier.open(); + await assert.rejects(toolPromise, err => { + return isCancellationError(err); + }, 'Expected tool call to be cancelled'); + }); }); diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts index ad4262a7dbd5b..31fbb654b6357 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts @@ -15,6 +15,8 @@ export class MockChatService implements IChatService { _serviceBrand: undefined; transferredSessionData: IChatTransferredSessionData | undefined; + private sessions = new Map(); + isEnabled(location: ChatAgentLocation): boolean { throw new Error('Method not implemented.'); } @@ -27,9 +29,12 @@ export class MockChatService implements IChatService { startSession(location: ChatAgentLocation, token: CancellationToken): ChatModel | undefined { throw new Error('Method not implemented.'); } + addSession(session: IChatModel): void { + this.sessions.set(session.sessionId, session); + } getSession(sessionId: string): IChatModel | undefined { // eslint-disable-next-line local/code-no-dangerous-type-assertions - return {} as IChatModel; + return this.sessions.get(sessionId) ?? {} as IChatModel; } getOrRestoreSession(sessionId: string): IChatModel | undefined { throw new Error('Method not implemented.'); diff --git a/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts b/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts index a192334cb9dc5..d854a0beb5e95 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts @@ -13,6 +13,9 @@ export class MockLanguageModelToolsService implements ILanguageModelToolsService constructor() { } + cancelToolCallsForRequest(requestId: string): void { + } + onDidChangeTools: Event = Event.None; registerToolData(toolData: IToolData): IDisposable { diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 7d07547770ee6..75db4dbb7f1fa 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -71,6 +71,8 @@ import { ITextModelService } from '../../../../../editor/common/services/resolve import { TextModelResolverService } from '../../../../services/textmodelResolver/common/textModelResolverService.js'; import { ChatInputBoxContentProvider } from '../../../chat/browser/chatEdinputInputContentProvider.js'; import { IObservable, observableValue } from '../../../../../base/common/observable.js'; +import { ILanguageModelToolsService } from '../../../chat/common/languageModelToolsService.js'; +import { MockLanguageModelToolsService } from '../../../chat/test/common/mockLanguageModelToolsService.js'; suite('InlineChatController', function () { @@ -198,6 +200,7 @@ suite('InlineChatController', function () { [IWorkbenchAssignmentService, new NullWorkbenchAssignmentService()], [ILanguageModelsService, new SyncDescriptor(LanguageModelsService)], [ITextModelService, new SyncDescriptor(TextModelResolverService)], + [ILanguageModelToolsService, new SyncDescriptor(MockLanguageModelToolsService)], ); instaService = store.add((store.add(workbenchInstantiationService(undefined, store))).createChild(serviceCollection)); From 77cec55e495ea7b9dceee6f589c781799e325d20 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 20 Dec 2024 08:53:35 +0100 Subject: [PATCH 155/200] Git - git installation welcome view should use remoteName context key (#236672) --- extensions/git/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index d63027619d183..53aa0fad747e3 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -3372,22 +3372,22 @@ { "view": "scm", "contents": "%view.workbench.scm.missing%", - "when": "config.git.enabled && git.missing" + "when": "config.git.enabled && git.missing && remoteName != ''" }, { "view": "scm", "contents": "%view.workbench.scm.missing.mac%", - "when": "config.git.enabled && git.missing && isMac" + "when": "config.git.enabled && git.missing && remoteName == '' && isMac" }, { "view": "scm", "contents": "%view.workbench.scm.missing.windows%", - "when": "config.git.enabled && git.missing && isWindows" + "when": "config.git.enabled && git.missing && remoteName == '' && isWindows" }, { "view": "scm", "contents": "%view.workbench.scm.missing.linux%", - "when": "config.git.enabled && git.missing && isLinux" + "when": "config.git.enabled && git.missing && remoteName == '' && isLinux" }, { "view": "scm", From da59ef74e2587e707944db56bcc0f84a9e6e3ff8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 20 Dec 2024 09:11:12 +0100 Subject: [PATCH 156/200] workbench.action.focus*GroupWithoutWrap commands fail to focus *-most group when sibebar/panel is focused (fix #236648) (#236673) --- src/vs/workbench/browser/parts/editor/editorCommands.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 1bbe6258eb05e..20c084c960325 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -312,6 +312,7 @@ function registerActiveEditorMoveCopyCommand(): void { } else if (sourceGroup.id !== targetGroup.id) { sourceGroup.copyEditors(editors.map(editor => ({ editor })), targetGroup); } + targetGroup.focus(); } } @@ -972,8 +973,8 @@ function registerFocusEditorGroupWihoutWrapCommands(): void { CommandsRegistry.registerCommand(command.id, async (accessor: ServicesAccessor) => { const editorGroupsService = accessor.get(IEditorGroupsService); - const group = editorGroupsService.findGroup({ direction: command.direction }, editorGroupsService.activeGroup, false); - group?.focus(); + const group = editorGroupsService.findGroup({ direction: command.direction }, editorGroupsService.activeGroup, false) ?? editorGroupsService.activeGroup; + group.focus(); }); } } From b9084edd1cf2414e21de4f9e5b5acae240a22218 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:08:22 +0100 Subject: [PATCH 157/200] Git - file system provider should throw `FileNotFound` if the resource does not exist in git instead of returning an empty file (#236676) --- extensions/git/src/fileSystemProvider.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/git/src/fileSystemProvider.ts b/extensions/git/src/fileSystemProvider.ts index af80924ae1386..24ae4e6df9a71 100644 --- a/extensions/git/src/fileSystemProvider.ts +++ b/extensions/git/src/fileSystemProvider.ts @@ -192,7 +192,8 @@ export class GitFileSystemProvider implements FileSystemProvider { try { return await repository.buffer(sanitizeRef(ref, path, repository), path); } catch (err) { - return new Uint8Array(0); + // File does not exist in git (ex: git ignored) + throw FileSystemError.FileNotFound(); } } From 91ced52bc8547ce1c21138ee4a6a5061cf995c92 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 20 Dec 2024 11:01:48 +0100 Subject: [PATCH 158/200] Add the possibility to define context keys on CodeEditorWidgets --- .../browser/widget/codeEditor/codeEditorWidget.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts index ed20f95580a8e..3bd2f0a903e7e 100644 --- a/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts @@ -290,6 +290,11 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE })); this._contextKeyService = this._register(contextKeyService.createScoped(this._domElement)); + if (codeEditorWidgetOptions.contextKeyValues) { + for (const [key, value] of Object.entries(codeEditorWidgetOptions.contextKeyValues)) { + this._contextKeyService.createKey(key, value); + } + } this._notificationService = notificationService; this._codeEditorService = codeEditorService; this._commandService = commandService; @@ -1988,6 +1993,12 @@ export interface ICodeEditorWidgetOptions { * Defaults to MenuId.SimpleEditorContext or MenuId.EditorContext depending on whether the widget is simple. */ contextMenuId?: MenuId; + + /** + * Define extra context keys that will be defined in the context service + * for the editor. + */ + contextKeyValues?: Record; } class ModelData { From 7d4b23f21adb12d47f582388ce96f9e447908f77 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 20 Dec 2024 11:02:35 +0100 Subject: [PATCH 159/200] Move `getOuterEditor` near the EmbeddedCodeEditorWidget --- .../widget/codeEditor/embeddedCodeEditorWidget.ts | 10 +++++++++- .../gotoSymbol/browser/peek/referencesController.ts | 3 ++- src/vs/editor/contrib/peekView/browser/peekView.ts | 11 +---------- .../workbench/contrib/scm/browser/quickDiffWidget.ts | 3 ++- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget.ts index b716aa6110977..3852374d39496 100644 --- a/src/vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget.ts @@ -13,7 +13,7 @@ import { ILanguageFeaturesService } from '../../../common/services/languageFeatu import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; @@ -61,3 +61,11 @@ export class EmbeddedCodeEditorWidget extends CodeEditorWidget { super.updateOptions(this._overwriteOptions); } } + +export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor | null { + const editor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); + if (editor instanceof EmbeddedCodeEditorWidget) { + return editor.getParentEditor(); + } + return editor; +} diff --git a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts index 175e4c8b49cce..48ba268a69905 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts @@ -14,7 +14,8 @@ import { Position } from '../../../../common/core/position.js'; import { Range } from '../../../../common/core/range.js'; import { IEditorContribution } from '../../../../common/editorCommon.js'; import { Location } from '../../../../common/languages.js'; -import { getOuterEditor, PeekContext } from '../../../peekView/browser/peekView.js'; +import { PeekContext } from '../../../peekView/browser/peekView.js'; +import { getOuterEditor } from '../../../../browser/widget/codeEditor/embeddedCodeEditorWidget.js'; import * as nls from '../../../../../nls.js'; import { CommandsRegistry } from '../../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; diff --git a/src/vs/editor/contrib/peekView/browser/peekView.ts b/src/vs/editor/contrib/peekView/browser/peekView.ts index 17084e7ae6418..c774617241fa9 100644 --- a/src/vs/editor/contrib/peekView/browser/peekView.ts +++ b/src/vs/editor/contrib/peekView/browser/peekView.ts @@ -16,7 +16,6 @@ import * as objects from '../../../../base/common/objects.js'; import './media/peekViewWidget.css'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { EditorContributionInstantiation, registerEditorContribution } from '../../../browser/editorExtensions.js'; -import { ICodeEditorService } from '../../../browser/services/codeEditorService.js'; import { EmbeddedCodeEditorWidget } from '../../../browser/widget/codeEditor/embeddedCodeEditorWidget.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; import { IEditorContribution } from '../../../common/editorCommon.js'; @@ -25,7 +24,7 @@ import * as nls from '../../../../nls.js'; import { createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; -import { createDecorator, IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { activeContrastBorder, contrastBorder, editorForeground, editorInfoForeground, registerColor } from '../../../../platform/theme/common/colorRegistry.js'; export const IPeekViewService = createDecorator('IPeekViewService'); @@ -79,14 +78,6 @@ class PeekContextController implements IEditorContribution { registerEditorContribution(PeekContextController.ID, PeekContextController, EditorContributionInstantiation.Eager); // eager because it needs to define a context key -export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor | null { - const editor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); - if (editor instanceof EmbeddedCodeEditorWidget) { - return editor.getParentEditor(); - } - return editor; -} - export interface IPeekViewStyles extends IStyles { headerBackgroundColor?: Color; primaryHeadingColor?: Color; diff --git a/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts b/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts index 8ac67f6cd539b..37ed63fb396f1 100644 --- a/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts +++ b/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts @@ -13,7 +13,7 @@ import { ISelectOptionItem } from '../../../../base/browser/ui/selectBox/selectB import { SelectActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import { defaultSelectBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js'; import { IColorTheme, IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { getOuterEditor, peekViewBorder, peekViewTitleBackground, peekViewTitleForeground, peekViewTitleInfoForeground, PeekViewWidget } from '../../../../editor/contrib/peekView/browser/peekView.js'; +import { peekViewBorder, peekViewTitleBackground, peekViewTitleForeground, peekViewTitleInfoForeground, PeekViewWidget } from '../../../../editor/contrib/peekView/browser/peekView.js'; import { editorBackground } from '../../../../platform/theme/common/colorRegistry.js'; import { IMenu, IMenuService, MenuId, MenuItemAction, MenuRegistry } from '../../../../platform/actions/common/actions.js'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../../editor/browser/editorBrowser.js'; @@ -48,6 +48,7 @@ import { gotoNextLocation, gotoPreviousLocation } from '../../../../platform/the import { Codicon } from '../../../../base/common/codicons.js'; import { Color } from '../../../../base/common/color.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import { getOuterEditor } from '../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js'; export const isQuickDiffVisible = new RawContextKey('dirtyDiffVisible', false); From 9cb7a27bd0bdecd16003711015d0a5172d5a8804 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 20 Dec 2024 11:14:30 +0100 Subject: [PATCH 160/200] Make Tab and Escape work when focus is in the preview --- .../browser/controller/commands.ts | 28 +++++++++++++++---- .../controller/inlineCompletionContextKeys.ts | 3 ++ .../controller/inlineCompletionsController.ts | 14 +++++++++- .../view/inlineEdits/sideBySideDiff.ts | 9 ++++-- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts index f183f9f896fff..2fe48fc4eb9bc 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts @@ -11,7 +11,8 @@ import { Action2, MenuId } from '../../../../../platform/actions/common/actions. import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; -import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { KeybindingsRegistry, KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { INotificationService, Severity } from '../../../../../platform/notification/common/notification.js'; import { ICodeEditor } from '../../../../browser/editorBrowser.js'; import { EditorAction, EditorCommand, ServicesAccessor } from '../../../../browser/editorExtensions.js'; import { EditorContextKeys } from '../../../../common/editorContextKeys.js'; @@ -19,7 +20,6 @@ import { Context as SuggestContext } from '../../../suggest/browser/suggest.js'; import { inlineSuggestCommitId, showNextInlineSuggestionActionId, showPreviousInlineSuggestionActionId } from './commandIds.js'; import { InlineCompletionContextKeys } from './inlineCompletionContextKeys.js'; import { InlineCompletionsController } from './inlineCompletionsController.js'; -import { INotificationService, Severity } from '../../../../../platform/notification/common/notification.js'; export class ShowNextInlineSuggestionAction extends EditorAction { public static ID = showNextInlineSuggestionActionId; @@ -211,14 +211,21 @@ export class AcceptInlineCompletion extends EditorAction { }); } - public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { - const controller = InlineCompletionsController.get(editor); + public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + const controller = InlineCompletionsController.getInFocusedEditorOrParent(accessor); if (controller) { controller.model.get()?.accept(controller.editor); controller.editor.focus(); } } } +KeybindingsRegistry.registerKeybindingRule({ + id: inlineSuggestCommitId, + weight: 202, // greater than jump + primary: KeyCode.Tab, + when: ContextKeyExpr.and(InlineCompletionContextKeys.inInlineEditsPreviewEditor) +}); + export class JumpToNextInlineEdit extends EditorAction { constructor() { @@ -276,14 +283,23 @@ export class HideInlineCompletion extends EditorAction { }); } - public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { - const controller = InlineCompletionsController.get(editor); + public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + const controller = InlineCompletionsController.getInFocusedEditorOrParent(accessor); transaction(tx => { controller?.model.get()?.stop('explicitCancel', tx); }); + controller?.editor.focus(); } } +KeybindingsRegistry.registerKeybindingRule({ + id: HideInlineCompletion.ID, + weight: -1, // very weak + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape], + when: ContextKeyExpr.and(InlineCompletionContextKeys.inInlineEditsPreviewEditor) +}); + export class ToggleAlwaysShowInlineSuggestionToolbar extends Action2 { public static ID = 'editor.action.inlineSuggest.toggleAlwaysShowToolbar'; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts index 7fbd4dc19fc31..834adac93caa9 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts @@ -5,6 +5,7 @@ import { RawContextKey } from '../../../../../platform/contextkey/common/contextkey.js'; import { localize } from '../../../../../nls.js'; +import * as nls from '../../../../../nls.js'; export abstract class InlineCompletionContextKeys { @@ -19,4 +20,6 @@ export abstract class InlineCompletionContextKeys { 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 static readonly inInlineEditsPreviewEditor = new RawContextKey('inInlineEditsPreviewEditor', true, nls.localize('inInlineEditsPreviewEditor', "Whether the current code editor is showing an inline edits preview")); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index 79fdfb85afdee..b2b4b8ca6c696 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -16,7 +16,7 @@ import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../.. 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 { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; import { hotClassGetOriginalInstance } from '../../../../../platform/observable/common/wrapInHotClass.js'; import { CoreEditingCommands } from '../../../../browser/coreCommands.js'; @@ -36,6 +36,7 @@ import { ObservableContextKeyService } from '../utils.js'; import { inlineSuggestCommitId } from './commandIds.js'; import { InlineCompletionContextKeys } from './inlineCompletionContextKeys.js'; import { InlineCompletionsView } from '../view/inlineCompletionsView.js'; +import { getOuterEditor } from '../../../../browser/widget/codeEditor/embeddedCodeEditorWidget.js'; export class InlineCompletionsController extends Disposable { private static readonly _instances = new Set(); @@ -43,6 +44,17 @@ export class InlineCompletionsController extends Disposable { public static hot = createHotClass(InlineCompletionsController); public static ID = 'editor.contrib.inlineCompletionsController'; + /** + * Find the controller in the focused editor or in the outer editor (if applicable) + */ + public static getInFocusedEditorOrParent(accessor: ServicesAccessor): InlineCompletionsController | null { + const outerEditor = getOuterEditor(accessor); + if (!outerEditor) { + return null; + } + return InlineCompletionsController.get(outerEditor); + } + public static get(editor: ICodeEditor): InlineCompletionsController | null { return hotClassGetOriginalInstance(editor.getContribution(InlineCompletionsController.ID)); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts index b34dc44ac1df7..042dc653cf90a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts @@ -28,11 +28,11 @@ import { Range } from '../../../../../common/core/range.js'; import { Command } from '../../../../../common/languages.js'; import { ITextModel } from '../../../../../common/model.js'; import { StickyScrollController } from '../../../../stickyScroll/browser/stickyScrollController.js'; +import { InlineCompletionContextKeys } from '../../controller/inlineCompletionContextKeys.js'; import { CustomizedMenuWorkbenchToolBar } from '../../hintsWidget/inlineCompletionsHintsWidget.js'; import { PathBuilder, StatusBarViewItem, getOffsetForPos, mapOutFalsy, maxContentWidthInRange, n } from './utils.js'; import { InlineEditWithChanges } from './viewAndDiffProducer.js'; - export const originalBackgroundColor = registerColor( 'inlineEdit.originalBackground', Color.transparent, @@ -271,7 +271,12 @@ export class InlineEditsSideBySideDiff extends Disposable { wordWrapOverride1: 'off', wordWrapOverride2: 'off', }, - { contributions: [], }, + { + contextKeyValues: { + [InlineCompletionContextKeys.inInlineEditsPreviewEditor.key]: true, + }, + contributions: [], + }, this._editor )); From 1730c76f6b3f2d6ab5819031cded0a6e24d54b6e Mon Sep 17 00:00:00 2001 From: zWing <371657110@qq.com> Date: Fri, 20 Dec 2024 18:29:31 +0800 Subject: [PATCH 161/200] fix(git-ext): fix limitWarning block the git status progress (#226577) --- extensions/git/src/repository.ts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 0c4b50cd5e5cd..9e6e0196abeb4 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -2359,24 +2359,26 @@ export class Repository implements Disposable { const yes = { title: l10n.t('Yes') }; const no = { title: l10n.t('No') }; - const result = await window.showWarningMessage(`${gitWarn} ${addKnown}`, yes, no, neverAgain); - if (result === yes) { - this.ignore([Uri.file(folderPath)]); - } else { + window.showWarningMessage(`${gitWarn} ${addKnown}`, yes, no, neverAgain).then(result => { + if (result === yes) { + this.ignore([Uri.file(folderPath)]); + } else { + if (result === neverAgain) { + config.update('ignoreLimitWarning', true, false); + } + + this.didWarnAboutLimit = true; + } + }); + } else { + const ok = { title: l10n.t('OK') }; + window.showWarningMessage(gitWarn, ok, neverAgain).then(result => { if (result === neverAgain) { config.update('ignoreLimitWarning', true, false); } this.didWarnAboutLimit = true; - } - } else { - const ok = { title: l10n.t('OK') }; - const result = await window.showWarningMessage(gitWarn, ok, neverAgain); - if (result === neverAgain) { - config.update('ignoreLimitWarning', true, false); - } - - this.didWarnAboutLimit = true; + }); } } From c1cff695d70848d892cc09908996e7f7a790e374 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 20 Dec 2024 11:40:15 +0100 Subject: [PATCH 162/200] Consistent layout actions for views/containers/activitybar (#236686) consistent layout actions --- .../browser/actions/layoutActions.ts | 44 +++---------------- .../parts/activitybar/activitybarPart.ts | 25 ++++++----- .../parts/auxiliarybar/auxiliaryBarActions.ts | 4 +- .../browser/parts/paneCompositeBar.ts | 2 +- .../browser/parts/panel/panelActions.ts | 13 +----- .../views/browser/viewDescriptorService.ts | 8 +--- 6 files changed, 25 insertions(+), 71 deletions(-) diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 75253e235de85..aed2fe31f746c 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -181,17 +181,6 @@ MenuRegistry.appendMenuItems([{ when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), order: 1 } -}, { - id: MenuId.ViewTitleContext, - item: { - group: '3_workbench_layout_move', - command: { - id: ToggleSidebarPositionAction.ID, - title: localize('move sidebar right', "Move Primary Side Bar Right") - }, - when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), - order: 1 - } }, { id: MenuId.ViewContainerTitleContext, item: { @@ -204,36 +193,25 @@ MenuRegistry.appendMenuItems([{ order: 1 } }, { - id: MenuId.ViewTitleContext, - item: { - group: '3_workbench_layout_move', - command: { - id: ToggleSidebarPositionAction.ID, - title: localize('move sidebar left', "Move Primary Side Bar Left") - }, - when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), - order: 1 - } -}, { - id: MenuId.ViewTitleContext, + id: MenuId.ViewContainerTitleContext, item: { group: '3_workbench_layout_move', command: { id: ToggleSidebarPositionAction.ID, title: localize('move second sidebar left', "Move Secondary Side Bar Left") }, - when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), + when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), order: 1 } }, { - id: MenuId.ViewTitleContext, + id: MenuId.ViewContainerTitleContext, item: { group: '3_workbench_layout_move', command: { id: ToggleSidebarPositionAction.ID, title: localize('move second sidebar right', "Move Secondary Side Bar Right") }, - when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), + when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), order: 1 } }]); @@ -291,9 +269,10 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { // Toggle Sidebar Visibility -class ToggleSidebarVisibilityAction extends Action2 { +export class ToggleSidebarVisibilityAction extends Action2 { static readonly ID = 'workbench.action.toggleSidebarVisibility'; + static readonly LABEL = localize('compositePart.hideSideBarLabel', "Hide Primary Side Bar"); constructor() { super({ @@ -349,17 +328,6 @@ MenuRegistry.appendMenuItems([ when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), order: 2 } - }, { - id: MenuId.ViewTitleContext, - item: { - group: '3_workbench_layout_move', - command: { - id: ToggleSidebarVisibilityAction.ID, - title: localize('compositePart.hideSideBarLabel', "Hide Primary Side Bar"), - }, - when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), - order: 2 - } }, { id: MenuId.LayoutControlMenu, item: { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 0935255446a36..f2af5fb3a2f24 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -11,7 +11,7 @@ import { Part } from '../../part.js'; import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position } from '../../../services/layout/browser/layoutService.js'; import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; -import { ToggleSidebarPositionAction } from '../../actions/layoutActions.js'; +import { ToggleSidebarPositionAction, ToggleSidebarVisibilityAction } from '../../actions/layoutActions.js'; import { IThemeService, IColorTheme, registerThemingParticipant } from '../../../../platform/theme/common/themeService.js'; import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BORDER, ACTIVITY_BAR_ACTIVE_FOCUS_BORDER } from '../../../common/theme.js'; import { activeContrastBorder, contrastBorder, focusBorder } from '../../../../platform/theme/common/colorRegistry.js'; @@ -371,10 +371,16 @@ export class ActivityBarCompositeBar extends PaneCompositeBar { getActivityBarContextMenuActions(): IAction[] { const activityBarPositionMenu = this.menuService.getMenuActions(MenuId.ActivityBarPositionMenu, this.contextKeyService, { shouldForwardArgs: true, renderShortTitle: true }); const positionActions = getContextMenuActions(activityBarPositionMenu).secondary; - return [ + const actions = [ new SubmenuAction('workbench.action.panel.position', localize('activity bar position', "Activity Bar Position"), positionActions), - toAction({ id: ToggleSidebarPositionAction.ID, label: ToggleSidebarPositionAction.getLabel(this.layoutService), run: () => this.instantiationService.invokeFunction(accessor => new ToggleSidebarPositionAction().run(accessor)) }) + toAction({ id: ToggleSidebarPositionAction.ID, label: ToggleSidebarPositionAction.getLabel(this.layoutService), run: () => this.instantiationService.invokeFunction(accessor => new ToggleSidebarPositionAction().run(accessor)) }), ]; + + if (this.part === Parts.SIDEBAR_PART) { + actions.push(toAction({ id: ToggleSidebarVisibilityAction.ID, label: ToggleSidebarVisibilityAction.LABEL, run: () => this.instantiationService.invokeFunction(accessor => new ToggleSidebarVisibilityAction().run(accessor)) })); + } + + return actions; } } @@ -493,15 +499,10 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { MenuRegistry.appendMenuItem(MenuId.ViewContainerTitleContext, { submenu: MenuId.ActivityBarPositionMenu, title: localize('positionActivituBar', "Activity Bar Position"), - when: ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar)), - group: '3_workbench_layout_move', - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.ViewTitleContext, { - submenu: MenuId.ActivityBarPositionMenu, - title: localize('positionActivituBar', "Activity Bar Position"), - when: ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar)), + when: ContextKeyExpr.or( + ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar)), + ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar)) + ), group: '3_workbench_layout_move', order: 1 }); diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts index e564a8d49d778..e5cc8c365503a 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts @@ -149,14 +149,14 @@ MenuRegistry.appendMenuItems([ order: 2 } }, { - id: MenuId.ViewTitleContext, + id: MenuId.ViewContainerTitleContext, item: { group: '3_workbench_layout_move', command: { id: ToggleAuxiliaryBarAction.ID, title: localize2('hideAuxiliaryBar', 'Hide Secondary Side Bar'), }, - when: ContextKeyExpr.and(AuxiliaryBarVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), + when: ContextKeyExpr.and(AuxiliaryBarVisibleContext, ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), order: 2 } } diff --git a/src/vs/workbench/browser/parts/paneCompositeBar.ts b/src/vs/workbench/browser/parts/paneCompositeBar.ts index 05b43a296e172..89d07a87bc034 100644 --- a/src/vs/workbench/browser/parts/paneCompositeBar.ts +++ b/src/vs/workbench/browser/parts/paneCompositeBar.ts @@ -99,7 +99,7 @@ export class PaneCompositeBar extends Disposable { constructor( protected readonly options: IPaneCompositeBarOptions, - private readonly part: Parts, + protected readonly part: Parts, private readonly paneCompositePart: IPaneCompositePart, @IInstantiationService protected readonly instantiationService: IInstantiationService, @IStorageService private readonly storageService: IStorageService, diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index b3be54f49d789..6e43176a62e1a 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -14,7 +14,7 @@ import { ContextKeyExpr, ContextKeyExpression } from '../../../../platform/conte import { Codicon } from '../../../../base/common/codicons.js'; import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; -import { ViewContainerLocationToString, ViewContainerLocation, IViewDescriptorService } from '../../../common/views.js'; +import { ViewContainerLocation, IViewDescriptorService } from '../../../common/views.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; @@ -326,17 +326,6 @@ MenuRegistry.appendMenuItems([ when: ContextKeyExpr.or(ContextKeyExpr.equals('config.workbench.layoutControl.type', 'toggles'), ContextKeyExpr.equals('config.workbench.layoutControl.type', 'both')), order: 1 } - }, { - id: MenuId.ViewTitleContext, - item: { - group: '3_workbench_layout_move', - command: { - id: TogglePanelAction.ID, - title: localize2('hidePanel', 'Hide Panel'), - }, - when: ContextKeyExpr.and(PanelVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Panel))), - order: 2 - } } ]); diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index ad59c3e2a2a45..c667e719ae4d5 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -792,16 +792,12 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor order: index, }, { id: MenuId.ViewContainerTitleContext, - when: ContextKeyExpr.and( - ContextKeyExpr.equals('viewContainer', viewContainerModel.viewContainer.id), - ), + when: ContextKeyExpr.equals('viewContainer', viewContainerModel.viewContainer.id), order: index, group: '1_toggleVisibility' }, { id: MenuId.ViewTitleContext, - when: ContextKeyExpr.and( - ContextKeyExpr.or(...viewContainerModel.visibleViewDescriptors.map(v => ContextKeyExpr.equals('view', v.id))) - ), + when: ContextKeyExpr.or(...viewContainerModel.visibleViewDescriptors.map(v => ContextKeyExpr.equals('view', v.id))), order: index, group: '2_toggleVisibility' }] From d239347a1c75707cfa7d9b4d9bfe5b7f28277c37 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 20 Dec 2024 12:05:21 +0100 Subject: [PATCH 163/200] Add a context key for whether commenting is enabled (#236679) --- .../workbench/contrib/comments/browser/commentService.ts | 3 +++ .../contrib/comments/common/commentContextKeys.ts | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index ca5a8aa59d918..b7d1cde9aead1 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -167,6 +167,7 @@ export class CommentService extends Disposable implements ICommentService { private _commentMenus = new Map(); private _isCommentingEnabled: boolean = true; private _workspaceHasCommenting: IContextKey; + private _commentingEnabled: IContextKey; private _continueOnComments = new Map(); // uniqueOwner -> PendingCommentThread[] private _continueOnCommentProviders = new Set(); @@ -190,6 +191,7 @@ export class CommentService extends Disposable implements ICommentService { this._handleConfiguration(); this._handleZenMode(); this._workspaceHasCommenting = CommentContextKeys.WorkspaceHasCommenting.bindTo(contextKeyService); + this._commentingEnabled = CommentContextKeys.commentingEnabled.bindTo(contextKeyService); const storageListener = this._register(new DisposableStore()); const storageEvent = Event.debounce(this.storageService.onDidChangeValue(StorageScope.WORKSPACE, CONTINUE_ON_COMMENTS, storageListener), (last, event) => last?.external ? last : event, 500); @@ -277,6 +279,7 @@ export class CommentService extends Disposable implements ICommentService { enableCommenting(enable: boolean): void { if (enable !== this._isCommentingEnabled) { this._isCommentingEnabled = enable; + this._commentingEnabled.set(enable); this._onDidChangeCommentingEnabled.fire(enable); } } diff --git a/src/vs/workbench/contrib/comments/common/commentContextKeys.ts b/src/vs/workbench/contrib/comments/common/commentContextKeys.ts index 210465efe6f7a..2a5d0776c450a 100644 --- a/src/vs/workbench/contrib/comments/common/commentContextKeys.ts +++ b/src/vs/workbench/contrib/comments/common/commentContextKeys.ts @@ -66,4 +66,12 @@ export namespace CommentContextKeys { * The comment widget is focused. */ export const commentFocused = new RawContextKey('commentFocused', false, { type: 'boolean', description: nls.localize('commentFocused', "Set when the comment is focused") }); + + /** + * A context key that is set when commenting is enabled. + */ + export const commentingEnabled = new RawContextKey('commentingEnabled', true, { + description: nls.localize('commentingEnabled', "Whether commenting functionality is enabled"), + type: 'boolean' + }); } From 692fbdbd4d72edbd6bb2507f6a71d320f3a12647 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 04:52:33 -0800 Subject: [PATCH 164/200] Set multi-line setting to a valid value when disabling Fixes #231562 --- .../terminalContrib/clipboard/browser/terminalClipboard.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/clipboard/browser/terminalClipboard.ts b/src/vs/workbench/contrib/terminalContrib/clipboard/browser/terminalClipboard.ts index ae9dc0b6699de..e8ba759f61fd0 100644 --- a/src/vs/workbench/contrib/terminalContrib/clipboard/browser/terminalClipboard.ts +++ b/src/vs/workbench/contrib/terminalContrib/clipboard/browser/terminalClipboard.ts @@ -95,7 +95,7 @@ export async function shouldPasteTerminalText(accessor: ServicesAccessor, text: } if (result.confirmed && checkboxChecked) { - await configurationService.updateValue(TerminalSettingId.EnableMultiLinePasteWarning, false); + await configurationService.updateValue(TerminalSettingId.EnableMultiLinePasteWarning, 'never'); } if (result.singleLine) { From 78cd9519c5c7b2b8ddb0a092e658d240db6bcbe0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 05:10:57 -0800 Subject: [PATCH 165/200] Close the terminal view when there's only one visible view Part of #236517 --- .../workbench/contrib/terminal/browser/terminalGroupService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts index ff4c4103eba76..6f5040cd4d776 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts @@ -83,7 +83,7 @@ export class TerminalGroupService extends Disposable implements ITerminalGroupSe hidePanel(): void { // Hide the panel if the terminal is in the panel and it has no sibling views const panel = this._viewDescriptorService.getViewContainerByViewId(TERMINAL_VIEW_ID); - if (panel && this._viewDescriptorService.getViewContainerModel(panel).activeViewDescriptors.length === 1) { + if (panel && this._viewDescriptorService.getViewContainerModel(panel).visibleViewDescriptors.length === 1) { this._viewsService.closeView(TERMINAL_VIEW_ID); TerminalContextKeys.tabsMouse.bindTo(this._contextKeyService).set(false); } From 2dc420d6387896b49298a05288704ccf75220271 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 05:31:07 -0800 Subject: [PATCH 166/200] Create terminal when toggled without contents Fixes #236517 --- .../contrib/terminal/browser/terminalView.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index b71064b87f0cb..38e6d34294218 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -57,6 +57,11 @@ export class TerminalViewPane extends ViewPane { private _terminalTabbedView?: TerminalTabbedView; get terminalTabbedView(): TerminalTabbedView | undefined { return this._terminalTabbedView; } private _isInitialized: boolean = false; + /** + * Tracks an active promise of terminal creation requested by this component. This helps prevent + * double creation for example when toggling a terminal's visibility and focusing it. + */ + private _isTerminalBeingCreated: boolean = false; private readonly _newDropdown: MutableDisposable = this._register(new MutableDisposable()); private readonly _dropdownMenu: IMenu; private readonly _singleTabMenu: IMenu; @@ -164,7 +169,8 @@ export class TerminalViewPane extends ViewPane { if (!wasInitialized) { switch (hideOnStartup) { case 'never': - this._terminalService.createTerminal({ location: TerminalLocation.Panel }); + this._isTerminalBeingCreated = true; + this._terminalService.createTerminal({ location: TerminalLocation.Panel }).finally(() => this._isTerminalBeingCreated = false); break; case 'whenEmpty': if (this._terminalService.restoredGroupCount === 0) { @@ -175,7 +181,10 @@ export class TerminalViewPane extends ViewPane { return; } - this._terminalService.createTerminal({ location: TerminalLocation.Panel }); + if (!this._isTerminalBeingCreated) { + this._isTerminalBeingCreated = true; + this._terminalService.createTerminal({ location: TerminalLocation.Panel }).finally(() => this._isTerminalBeingCreated = false); + } } } @@ -320,6 +329,10 @@ export class TerminalViewPane extends ViewPane { override focus() { super.focus(); if (this._terminalService.connectionState === TerminalConnectionState.Connected) { + if (this._terminalGroupService.instances.length === 0 && !this._isTerminalBeingCreated) { + this._isTerminalBeingCreated = true; + this._terminalService.createTerminal({ location: TerminalLocation.Panel }).finally(() => this._isTerminalBeingCreated = false); + } this._terminalGroupService.showPanel(true); return; } From 8d0d731b7c53b7656e9a7014b14b797dd1796a1a Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 20 Dec 2024 14:31:20 +0100 Subject: [PATCH 167/200] SCM - add scm.historyProviderCount context key (#236694) --- .../contrib/scm/browser/scm.contribution.ts | 12 ++----- .../contrib/scm/common/scmService.ts | 34 +++++++++++++++---- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 0c2d7c1af5f1d..09ecabe67eeff 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -38,8 +38,6 @@ import { IViewsService } from '../../../services/views/common/viewsService.js'; import { IListService, WorkbenchList } from '../../../../platform/list/browser/listService.js'; import { isSCMRepository } from './util.js'; import { SCMHistoryViewPane } from './scmHistoryViewPane.js'; -import { IsWebContext } from '../../../../platform/contextkey/common/contextkeys.js'; -import { RemoteNameContext } from '../../../common/contextkeys.js'; import { QuickDiffModelService, IQuickDiffModelService } from './quickDiffModel.js'; import { QuickDiffEditorController } from './quickDiffWidget.js'; import { EditorContributionInstantiation, registerEditorContribution } from '../../../../editor/browser/editorExtensions.js'; @@ -137,14 +135,8 @@ viewsRegistry.registerViews([{ weight: 40, order: 2, when: ContextKeyExpr.and( - // Repository Count - ContextKeyExpr.and( - ContextKeyExpr.has('scm.providerCount'), - ContextKeyExpr.notEquals('scm.providerCount', 0)), - // Not Serverless - ContextKeyExpr.and( - IsWebContext, - RemoteNameContext.isEqualTo(''))?.negate() + ContextKeyExpr.has('scm.historyProviderCount'), + ContextKeyExpr.notEquals('scm.historyProviderCount', 0), ), containerIcon: sourceControlViewIcon }], viewContainer); diff --git a/src/vs/workbench/contrib/scm/common/scmService.ts b/src/vs/workbench/contrib/scm/common/scmService.ts index c9af734400a52..14c69f73b0d7f 100644 --- a/src/vs/workbench/contrib/scm/common/scmService.ts +++ b/src/vs/workbench/contrib/scm/common/scmService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; import { Event, Emitter } from '../../../../base/common/event.js'; import { ISCMService, ISCMProvider, ISCMInput, ISCMRepository, IInputValidator, ISCMInputChangeEvent, SCMInputChangeReason, InputValidationType, IInputValidation } from './scm.js'; import { ILogService } from '../../../../platform/log/common/log.js'; @@ -17,6 +17,7 @@ import { Iterable } from '../../../../base/common/iterator.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; import { Schemas } from '../../../../base/common/network.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; +import { runOnChange } from '../../../../base/common/observable.js'; class SCMInput extends Disposable implements ISCMInput { @@ -188,7 +189,7 @@ class SCMRepository implements ISCMRepository { constructor( public readonly id: string, public readonly provider: ISCMProvider, - private disposable: IDisposable, + private readonly disposables: DisposableStore, inputHistory: SCMInputHistory ) { this.input = new SCMInput(this, inputHistory); @@ -204,7 +205,7 @@ class SCMRepository implements ISCMRepository { } dispose(): void { - this.disposable.dispose(); + this.disposables.dispose(); this.provider.dispose(); } } @@ -355,6 +356,7 @@ export class SCMService implements ISCMService { private inputHistory: SCMInputHistory; private providerCount: IContextKey; + private historyProviderCount: IContextKey; private readonly _onDidAddProvider = new Emitter(); readonly onDidAddRepository: Event = this._onDidAddProvider.event; @@ -370,7 +372,9 @@ export class SCMService implements ISCMService { @IUriIdentityService private readonly uriIdentityService: IUriIdentityService ) { this.inputHistory = new SCMInputHistory(storageService, workspaceContextService); + this.providerCount = contextKeyService.createKey('scm.providerCount', 0); + this.historyProviderCount = contextKeyService.createKey('scm.historyProviderCount', 0); } registerSCMProvider(provider: ISCMProvider): ISCMRepository { @@ -380,17 +384,33 @@ export class SCMService implements ISCMService { throw new Error(`SCM Provider ${provider.id} already exists.`); } - const disposable = toDisposable(() => { + const disposables = new DisposableStore(); + + const historyProviderCount = () => { + return Array.from(this._repositories.values()) + .filter(r => !!r.provider.historyProvider).length; + }; + + disposables.add(toDisposable(() => { this._repositories.delete(provider.id); this._onDidRemoveProvider.fire(repository); + this.providerCount.set(this._repositories.size); - }); + this.historyProviderCount.set(historyProviderCount()); + })); - const repository = new SCMRepository(provider.id, provider, disposable, this.inputHistory); + const repository = new SCMRepository(provider.id, provider, disposables, this.inputHistory); this._repositories.set(provider.id, repository); - this._onDidAddProvider.fire(repository); + + disposables.add(runOnChange(provider.historyProvider, () => { + this.historyProviderCount.set(historyProviderCount()); + })); this.providerCount.set(this._repositories.size); + this.historyProviderCount.set(historyProviderCount()); + + this._onDidAddProvider.fire(repository); + return repository; } From f7fd6660f9f11e5a270fee834ca0382416ba9dfe Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 05:35:12 -0800 Subject: [PATCH 168/200] Add hideOnLastClosed setting Fixes #236517 --- src/vs/platform/terminal/common/terminal.ts | 1 + src/vs/workbench/contrib/terminal/browser/terminalService.ts | 2 +- src/vs/workbench/contrib/terminal/common/terminal.ts | 1 + .../contrib/terminal/common/terminalConfiguration.ts | 5 +++++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 8235e8a0ab40c..246ccaf6a7657 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -103,6 +103,7 @@ export const enum TerminalSettingId { EnablePersistentSessions = 'terminal.integrated.enablePersistentSessions', PersistentSessionReviveProcess = 'terminal.integrated.persistentSessionReviveProcess', HideOnStartup = 'terminal.integrated.hideOnStartup', + HideOnLastClosed = 'terminal.integrated.hideOnLastClosed', CustomGlyphs = 'terminal.integrated.customGlyphs', RescaleOverlappingGlyphs = 'terminal.integrated.rescaleOverlappingGlyphs', PersistentSessionScrollback = 'terminal.integrated.persistentSessionScrollback', diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 698559d3b2ce9..7fea79a51a703 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -208,7 +208,7 @@ export class TerminalService extends Disposable implements ITerminalService { // down. When shutting down the panel is locked in place so that it is restored upon next // launch. this._register(this._terminalGroupService.onDidChangeActiveInstance(instance => { - if (!instance && !this._isShuttingDown) { + if (!instance && !this._isShuttingDown && this._terminalConfigService.config.hideOnLastClosed) { this._terminalGroupService.hidePanel(); } if (instance?.shellType) { diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index cf5c306bb7099..6fbd41d8637fa 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -211,6 +211,7 @@ export interface ITerminalConfiguration { experimental?: { windowsUseConptyDll?: boolean; }; + hideOnLastClosed: boolean; } export interface ITerminalFont { diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index b90a67cbbc634..6743ad25d3c93 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -549,6 +549,11 @@ const terminalConfiguration: IConfigurationNode = { ], default: 'never' }, + [TerminalSettingId.HideOnLastClosed]: { + description: localize('terminal.integrated.hideOnLastClosed', "Whether to hide the terminal view when the last terminal is closed. This will only happen when the terminal is the only visible view in the view container."), + type: 'boolean', + default: true + }, [TerminalSettingId.CustomGlyphs]: { markdownDescription: localize('terminal.integrated.customGlyphs', "Whether to draw custom glyphs for block element and box drawing characters instead of using the font, which typically yields better rendering with continuous lines. Note that this doesn't work when {0} is disabled.", `\`#${TerminalSettingId.GpuAcceleration}#\``), type: 'boolean', From 502a7e5d43b277bd4b8783f837a7cba65234e4f4 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 20 Dec 2024 15:16:06 +0100 Subject: [PATCH 169/200] SCM - do not show "Open in External Terminal" action when connected to a remote (#236697) --- .../contrib/scm/browser/scm.contribution.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 09ecabe67eeff..7f8e9c71a099e 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -41,6 +41,7 @@ import { SCMHistoryViewPane } from './scmHistoryViewPane.js'; import { QuickDiffModelService, IQuickDiffModelService } from './quickDiffModel.js'; import { QuickDiffEditorController } from './quickDiffWidget.js'; import { EditorContributionInstantiation, registerEditorContribution } from '../../../../editor/browser/editorExtensions.js'; +import { RemoteNameContext } from '../../../common/contextkeys.js'; ModesRegistry.registerLanguage({ id: 'scminput', @@ -529,7 +530,12 @@ MenuRegistry.appendMenuItem(MenuId.SCMSourceControl, { id: 'scm.openInTerminal', title: localize('open in external terminal', "Open in External Terminal") }, - when: ContextKeyExpr.and(ContextKeyExpr.equals('scmProviderHasRootUri', true), ContextKeyExpr.or(ContextKeyExpr.equals('config.terminal.sourceControlRepositoriesKind', 'external'), ContextKeyExpr.equals('config.terminal.sourceControlRepositoriesKind', 'both'))) + when: ContextKeyExpr.and( + RemoteNameContext.isEqualTo(''), + ContextKeyExpr.equals('scmProviderHasRootUri', true), + ContextKeyExpr.or( + ContextKeyExpr.equals('config.terminal.sourceControlRepositoriesKind', 'external'), + ContextKeyExpr.equals('config.terminal.sourceControlRepositoriesKind', 'both'))) }); MenuRegistry.appendMenuItem(MenuId.SCMSourceControl, { @@ -538,7 +544,11 @@ MenuRegistry.appendMenuItem(MenuId.SCMSourceControl, { id: 'scm.openInIntegratedTerminal', title: localize('open in integrated terminal', "Open in Integrated Terminal") }, - when: ContextKeyExpr.and(ContextKeyExpr.equals('scmProviderHasRootUri', true), ContextKeyExpr.or(ContextKeyExpr.equals('config.terminal.sourceControlRepositoriesKind', 'integrated'), ContextKeyExpr.equals('config.terminal.sourceControlRepositoriesKind', 'both'))) + when: ContextKeyExpr.and( + ContextKeyExpr.equals('scmProviderHasRootUri', true), + ContextKeyExpr.or( + ContextKeyExpr.equals('config.terminal.sourceControlRepositoriesKind', 'integrated'), + ContextKeyExpr.equals('config.terminal.sourceControlRepositoriesKind', 'both'))) }); KeybindingsRegistry.registerCommandAndKeybindingRule({ From d8c2678e9a4153f56a4160481b1c983558637b6a Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 20 Dec 2024 15:25:40 +0100 Subject: [PATCH 170/200] Add share context menu action (#236699) share context menu action --- src/vs/workbench/browser/parts/titlebar/titlebarActions.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index f84bf3f06b737..e4b8a3e6b9d66 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -64,6 +64,12 @@ registerAction2(class ToggleNavigationControl extends ToggleTitleBarConfigAction } }); +registerAction2(class ToggleShareControl extends ToggleTitleBarConfigAction { + constructor() { + super('workbench.experimental.share.enabled', localize('toggle.share', 'Share'), localize('toggle.shareDescription', "Toggle visibility of the Share action in title bar"), 2, false, ContextKeyExpr.has('config.window.commandCenter')); + } +}); + registerAction2(class ToggleLayoutControl extends ToggleTitleBarConfigAction { constructor() { super(LayoutSettings.LAYOUT_ACTIONS, localize('toggle.layout', 'Layout Controls'), localize('toggle.layoutDescription', "Toggle visibility of the Layout Controls in title bar"), 3, true); From b8b4db329e93816f34611d1a653057c323485463 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:51:58 +0100 Subject: [PATCH 171/200] Git - cleanup `vscode-merge-base` git config key when deleteing a branch (#236716) --- extensions/git/src/git.ts | 6 +++--- extensions/git/src/repository.ts | 11 +++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 87ea23e3a8538..62bae1422df0b 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1166,11 +1166,11 @@ export class Repository { return this.git.spawn(args, options); } - async config(scope: string, key: string, value: any = null, options: SpawnOptions = {}): Promise { - const args = ['config']; + async config(command: string, scope: string, key: string, value: any = null, options: SpawnOptions = {}): Promise { + const args = ['config', command]; if (scope) { - args.push('--' + scope); + args.push(`--${scope}`); } args.push(key); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 9e6e0196abeb4..ec66d510c720d 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1082,15 +1082,15 @@ export class Repository implements Disposable { } getConfig(key: string): Promise { - return this.run(Operation.Config(true), () => this.repository.config('local', key)); + return this.run(Operation.Config(true), () => this.repository.config('get', 'local', key)); } getGlobalConfig(key: string): Promise { - return this.run(Operation.Config(true), () => this.repository.config('global', key)); + return this.run(Operation.Config(true), () => this.repository.config('get', 'global', key)); } setConfig(key: string, value: string): Promise { - return this.run(Operation.Config(false), () => this.repository.config('local', key, value)); + return this.run(Operation.Config(false), () => this.repository.config('set', 'local', key, value)); } log(options?: LogOptions & { silent?: boolean }): Promise { @@ -1465,7 +1465,10 @@ export class Repository implements Disposable { } async deleteBranch(name: string, force?: boolean): Promise { - await this.run(Operation.DeleteBranch, () => this.repository.deleteBranch(name, force)); + return this.run(Operation.DeleteBranch, async () => { + await this.repository.deleteBranch(name, force); + await this.repository.config('unset', 'local', `branch.${name}.vscode-merge-base`); + }); } async renameBranch(name: string): Promise { From 5cdf4dd74bae97e2701de972294aa3e501c01920 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 08:14:37 -0800 Subject: [PATCH 172/200] Register editor gpu acceleration with included:false Fixes #235730 --- src/vs/editor/common/config/editorOptions.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index e355a960db99d..5fe2028e3666b 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -5833,15 +5833,14 @@ export const EditorOptions = { 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.") - // } + { + included: false, // Hide the setting from users while it's unstable + 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', From decaa08e9945fad06912b5aafbd8f0f0e88e11c2 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:15:05 +0100 Subject: [PATCH 173/200] Use correct context keys for share provider title context action (#236718) use correct context keys for share provider titile context action --- .../browser/parts/titlebar/titlebarActions.ts | 8 +------- .../contrib/chat/browser/actions/chatActions.ts | 2 +- .../workbench/contrib/share/browser/shareService.ts | 12 ++++++++++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index e4b8a3e6b9d66..e09172e4a7f23 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -64,15 +64,9 @@ registerAction2(class ToggleNavigationControl extends ToggleTitleBarConfigAction } }); -registerAction2(class ToggleShareControl extends ToggleTitleBarConfigAction { - constructor() { - super('workbench.experimental.share.enabled', localize('toggle.share', 'Share'), localize('toggle.shareDescription', "Toggle visibility of the Share action in title bar"), 2, false, ContextKeyExpr.has('config.window.commandCenter')); - } -}); - registerAction2(class ToggleLayoutControl extends ToggleTitleBarConfigAction { constructor() { - super(LayoutSettings.LAYOUT_ACTIONS, localize('toggle.layout', 'Layout Controls'), localize('toggle.layoutDescription', "Toggle visibility of the Layout Controls in title bar"), 3, true); + super(LayoutSettings.LAYOUT_ACTIONS, localize('toggle.layout', 'Layout Controls'), localize('toggle.layoutDescription', "Toggle visibility of the Layout Controls in title bar"), 4, true); } }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index d1d7d951559fa..f0ece5fa2b959 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -543,7 +543,7 @@ registerAction2(class ToggleCopilotControl extends ToggleTitleBarConfigAction { super( 'chat.commandCenter.enabled', localize('toggle.chatControl', 'Copilot Controls'), - localize('toggle.chatControlsDescription', "Toggle visibility of the Copilot Controls in title bar"), 4, false, + localize('toggle.chatControlsDescription', "Toggle visibility of the Copilot Controls in title bar"), 5, false, ContextKeyExpr.and( ChatContextKeys.supported, ContextKeyExpr.has('config.window.commandCenter') diff --git a/src/vs/workbench/contrib/share/browser/shareService.ts b/src/vs/workbench/contrib/share/browser/shareService.ts index 0d684acf736c4..9a94bd370bebe 100644 --- a/src/vs/workbench/contrib/share/browser/shareService.ts +++ b/src/vs/workbench/contrib/share/browser/shareService.ts @@ -9,11 +9,13 @@ import { URI } from '../../../../base/common/uri.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; import { score } from '../../../../editor/common/languageSelector.js'; import { localize } from '../../../../nls.js'; -import { ISubmenuItem } from '../../../../platform/actions/common/actions.js'; -import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { ISubmenuItem, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { ILabelService } from '../../../../platform/label/common/label.js'; import { IQuickInputService, IQuickPickItem } from '../../../../platform/quickinput/common/quickInput.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { ToggleTitleBarConfigAction } from '../../../browser/parts/titlebar/titlebarActions.js'; +import { WorkspaceFolderCountContext } from '../../../common/contextkeys.js'; import { IShareProvider, IShareService, IShareableItem } from '../common/share.js'; export const ShareProviderCountContext = new RawContextKey('shareProviderCount', 0, localize('shareProviderCount', "The number of available share providers")); @@ -84,3 +86,9 @@ export class ShareService implements IShareService { return; } } + +registerAction2(class ToggleShareControl extends ToggleTitleBarConfigAction { + constructor() { + super('workbench.experimental.share.enabled', localize('toggle.share', 'Share'), localize('toggle.shareDescription', "Toggle visibility of the Share action in title bar"), 3, false, ContextKeyExpr.and(ContextKeyExpr.has('config.window.commandCenter'), ContextKeyExpr.and(ShareProviderCountContext.notEqualsTo(0), WorkspaceFolderCountContext.notEqualsTo(0)))); + } +}); From 62948ea6d834ffed42b20fc0cba28ffaa3cf0cbe Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 08:23:25 -0800 Subject: [PATCH 174/200] Indicate when terminal sticky scroll is truncated Fixes #199974 --- .../stickyScroll/browser/terminalStickyScrollOverlay.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts index 7b3d3de337148..0da38ee599403 100644 --- a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts @@ -265,6 +265,7 @@ export class TerminalStickyScrollOverlay extends Disposable { const rowOffset = !isPartialCommand && command.endMarker ? Math.max(buffer.viewportY - command.endMarker.line + 1, 0) : 0; const maxLineCount = Math.min(this._rawMaxLineCount, Math.floor(xterm.rows * Constants.StickyScrollPercentageCap)); const stickyScrollLineCount = Math.min(promptRowCount + commandRowCount - 1, maxLineCount) - rowOffset; + const isTruncated = stickyScrollLineCount < promptRowCount + commandRowCount - 1; // Hide sticky scroll if it's currently on a line that contains it if (buffer.viewportY <= stickyScrollLineStart) { @@ -293,7 +294,7 @@ export class TerminalStickyScrollOverlay extends Disposable { start: stickyScrollLineStart + rowOffset, end: stickyScrollLineStart + rowOffset + Math.max(stickyScrollLineCount - 1, 0) } - }); + }) + (isTruncated ? '\x1b[0m …' : ''); // If a partial command's sticky scroll would show nothing, just hide it. This is another // edge case when using a pager or interactive editor. From 12708746496225474c020218d3eae298977266f5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:43:35 -0800 Subject: [PATCH 175/200] Suppress exit warning after ctrl+d Fixes #160325 --- src/vs/workbench/contrib/terminal/browser/terminal.ts | 5 +++++ .../workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- .../contrib/terminal/browser/xterm/xtermTerminal.ts | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 2a56aeca9795d..ead75598c0ec3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -1095,6 +1095,11 @@ export interface IXtermTerminal extends IDisposable { */ readonly isGpuAccelerated: boolean; + /** + * The last `onData` input event fired by {@link RawXtermTerminal.onData}. + */ + readonly lastInputEvent: string | undefined; + /** * Attached the terminal to the given element * @param container Container the terminal will be rendered in diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 0a1646792a030..df286dd974ad1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1598,7 +1598,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } else { if (exitMessage) { const failedDuringLaunch = this._processManager.processState === ProcessState.KilledDuringLaunch; - if (failedDuringLaunch || this._terminalConfigurationService.config.showExitAlert) { + if (failedDuringLaunch || (this._terminalConfigurationService.config.showExitAlert && this.xterm?.lastInputEvent !== /*Ctrl+D*/'\x04')) { // Always show launch failures this._notificationService.notify({ message: exitMessage, diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 5ef6d497196ea..4e860821f7e9b 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -96,6 +96,8 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach private static _suggestedRendererType: 'dom' | undefined = undefined; private _attached?: { container: HTMLElement; options: IXtermAttachToElementOptions }; private _isPhysicalMouseWheel = MouseWheelClassifier.INSTANCE.isPhysicalMouseWheel(); + private _lastInputEvent: string | undefined; + get lastInputEvent(): string | undefined { return this._lastInputEvent; } // Always on addons private _markNavigationAddon: MarkNavigationAddon; @@ -256,6 +258,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach this._anyFocusedTerminalHasSelection.set(this.raw.hasSelection()); } })); + this._register(this.raw.onData(e => this._lastInputEvent = e)); // Load addons this._updateUnicodeVersion(); From 1be76b3c97bbe6ce32822c2e7baac4d115376478 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 20 Dec 2024 09:47:26 -0800 Subject: [PATCH 176/200] Fix response toString for inline refs Fixes #236655 --- .../contrib/chat/common/chatModel.ts | 98 +++++++++++++------ .../Response_inline_reference.0.snap | 4 +- .../chat/test/common/chatModel.test.ts | 9 +- 3 files changed, 78 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 14fa03dffa0f7..3cf9a8585c786 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -9,6 +9,7 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { IMarkdownString, MarkdownString, isMarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { revive } from '../../../../base/common/marshalling.js'; +import { Schemas } from '../../../../base/common/network.js'; import { equals } from '../../../../base/common/objects.js'; import { basename, isEqual } from '../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; @@ -397,38 +398,12 @@ export class Response extends Disposable implements IResponse { } private _updateRepr(quiet?: boolean) { - const inlineRefToRepr = (part: IChatContentInlineReference) => - 'uri' in part.inlineReference - ? basename(part.inlineReference.uri) - : 'name' in part.inlineReference - ? part.inlineReference.name - : basename(part.inlineReference); - - this._responseRepr = this._responseParts.map(part => { - if (part.kind === 'treeData') { - return ''; - } else if (part.kind === 'inlineReference') { - return inlineRefToRepr(part); - } else if (part.kind === 'command') { - return part.command.title; - } else if (part.kind === 'textEditGroup') { - return localize('editsSummary', "Made changes."); - } else if (part.kind === 'progressMessage' || part.kind === 'codeblockUri' || part.kind === 'toolInvocation' || part.kind === 'toolInvocationSerialized') { - return ''; - } else if (part.kind === 'confirmation') { - return `${part.title}\n${part.message}`; - } else { - return part.content.value; - } - }) - .filter(s => s.length > 0) - .join('\n\n'); - + this._responseRepr = this.partsToRepr(this._responseParts); this._responseRepr += this._citations.length ? '\n\n' + getCodeCitationsMessage(this._citations) : ''; this._markdownContent = this._responseParts.map(part => { if (part.kind === 'inlineReference') { - return inlineRefToRepr(part); + return this.inlineRefToRepr(part); } else if (part.kind === 'markdownContent' || part.kind === 'markdownVuln') { return part.content.value; } else { @@ -442,6 +417,73 @@ export class Response extends Disposable implements IResponse { this._onDidChangeValue.fire(); } } + + private partsToRepr(parts: readonly IChatProgressResponseContent[]): string { + const blocks: string[] = []; + let currentBlockSegments: string[] = []; + + for (const part of parts) { + let segment: { text: string; isBlock?: boolean } | undefined; + switch (part.kind) { + case 'treeData': + case 'progressMessage': + case 'codeblockUri': + case 'toolInvocation': + case 'toolInvocationSerialized': + // Ignore + continue; + case 'inlineReference': + segment = { text: this.inlineRefToRepr(part) }; + break; + case 'command': + segment = { text: part.command.title, isBlock: true }; + break; + case 'textEditGroup': + segment = { text: localize('editsSummary', "Made changes."), isBlock: true }; + break; + case 'confirmation': + segment = { text: `${part.title}\n${part.message}`, isBlock: true }; + break; + default: + segment = { text: part.content.value }; + break; + } + + if (segment.isBlock) { + if (currentBlockSegments.length) { + blocks.push(currentBlockSegments.join('')); + currentBlockSegments = []; + } + blocks.push(segment.text); + } else { + currentBlockSegments.push(segment.text); + } + } + + if (currentBlockSegments.length) { + blocks.push(currentBlockSegments.join('')); + } + + return blocks.join('\n\n'); + } + + private inlineRefToRepr(part: IChatContentInlineReference) { + if ('uri' in part.inlineReference) { + return this.uriToRepr(part.inlineReference.uri); + } + + return 'name' in part.inlineReference + ? '`' + part.inlineReference.name + '`' + : this.uriToRepr(part.inlineReference); + } + + private uriToRepr(uri: URI): string { + if (uri.scheme === Schemas.http || uri.scheme === Schemas.https) { + return uri.toString(false); + } + + return basename(uri); + } } export class ChatResponseModel extends Disposable implements IChatResponseModel { diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_inline_reference.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_inline_reference.0.snap index b26d84334a081..3a54719571dfe 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_inline_reference.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_inline_reference.0.snap @@ -1,7 +1,7 @@ [ { content: { - value: "text before", + value: "text before ", isTrusted: false, supportThemeIcons: false, supportHtml: false @@ -14,7 +14,7 @@ }, { content: { - value: "text after", + value: " text after", isTrusted: false, supportThemeIcons: false, supportHtml: false diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index be40ba599bd0c..c9c2059b30710 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -191,10 +191,13 @@ suite('Response', () => { test('inline reference', async () => { const response = store.add(new Response([])); - response.updateContent({ content: new MarkdownString('text before'), kind: 'markdownContent' }); - response.updateContent({ inlineReference: URI.parse('https://microsoft.com'), kind: 'inlineReference' }); - response.updateContent({ content: new MarkdownString('text after'), kind: 'markdownContent' }); + response.updateContent({ content: new MarkdownString('text before '), kind: 'markdownContent' }); + response.updateContent({ inlineReference: URI.parse('https://microsoft.com/'), kind: 'inlineReference' }); + response.updateContent({ content: new MarkdownString(' text after'), kind: 'markdownContent' }); await assertSnapshot(response.value); + + assert.strictEqual(response.toString(), 'text before https://microsoft.com/ text after'); + }); }); From 437450a35c25538b5a8719aeee03d060be883f0c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:08:18 -0800 Subject: [PATCH 177/200] Explain exitCode undefined in more detail Fixes #236670 --- src/vscode-dts/vscode.d.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index c9ecff2b0b989..7edf86d0780c5 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -7724,10 +7724,17 @@ declare module 'vscode' { /** * The exit code reported by the shell. * - * Note that `undefined` means the shell either did not report an exit code (ie. the shell - * integration script is misbehaving) or the shell reported a command started before the command - * finished (eg. a sub-shell was opened). Generally this should not happen, depending on the use - * case, it may be best to treat this as a failure. + * When this is `undefined` it can mean several things: + * + * - The shell either did not report an exit code (ie. the shell integration script is + * misbehaving) + * - The shell reported a command started before the command finished (eg. a sub-shell was + * opened). + * - The user canceled the command via ctrl+c. + * - The user pressed enter when there was no input. + * + * Generally this should not happen. Depending on the use case, it may be best to treat this + * as a failure. * * @example * const execution = shellIntegration.executeCommand({ From dfdece69e67e45a2a563929608c69dad8de6fe6e Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 20 Dec 2024 12:23:36 -0600 Subject: [PATCH 178/200] fix types for task API props (#236735) fix #231858 --- src/vs/workbench/api/common/extHostTypes.ts | 6 ++++-- src/vscode-dts/vscode.d.ts | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 90f82a69ce06e..d388236e83791 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2343,7 +2343,9 @@ export class ShellExecution implements vscode.ShellExecution { throw illegalArgument('command'); } this._command = arg0; - this._args = arg1 as (string | vscode.ShellQuotedString)[]; + if (arg1) { + this._args = arg1; + } this._options = arg2; } else { if (typeof arg0 !== 'string') { @@ -2380,7 +2382,7 @@ export class ShellExecution implements vscode.ShellExecution { return this._args; } - set args(value: (string | vscode.ShellQuotedString)[]) { + set args(value: (string | vscode.ShellQuotedString)[] | undefined) { this._args = value || []; } diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index c9ecff2b0b989..3e389318075b2 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -8668,12 +8668,12 @@ declare module 'vscode' { /** * The shell command. Is `undefined` if created with a full command line. */ - command: string | ShellQuotedString; + command: string | ShellQuotedString | undefined; /** * The shell args. Is `undefined` if created with a full command line. */ - args: Array; + args: Array | undefined; /** * The shell options used when the command line is executed in a shell. From 23aee3d360c197e156556dd9a4984c813bd818dc Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 20 Dec 2024 12:28:50 -0600 Subject: [PATCH 179/200] Revert "Reland fix custom task shell doesn't work without manually passing in "run command" arg/flag" (#236726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "Reland fix custom task shell doesn't work without manually passing in…" This reverts commit 330ab6c2928963dd83a7d90a271d9f2eb8db90d2. --- src/vs/platform/terminal/common/terminal.ts | 1 - .../tasks/browser/terminalTaskSystem.ts | 33 ++++++++----------- .../browser/terminalProfileResolverService.ts | 1 - 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 246ccaf6a7657..97fc8e80462cd 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -872,7 +872,6 @@ export interface ITerminalProfile { overrideName?: boolean; color?: string; icon?: ThemeIcon | URI | { light: URI; dark: URI }; - isAutomationShell?: boolean; } export interface ITerminalDimensionsOverride extends Readonly { diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 3b2c1e51c7f07..e44eaa47cf96c 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -1112,6 +1112,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { color: task.configurationProperties.icon?.color || undefined, waitOnExit }; + let shellSpecified: boolean = false; const shellOptions: IShellConfiguration | undefined = task.command.options && task.command.options.shell; if (shellOptions) { if (shellOptions.executable) { @@ -1120,12 +1121,12 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { shellLaunchConfig.args = undefined; } shellLaunchConfig.executable = await this._resolveVariable(variableResolver, shellOptions.executable); + shellSpecified = true; } if (shellOptions.args) { shellLaunchConfig.args = await this._resolveVariables(variableResolver, shellOptions.args.slice()); } } - const taskShellArgsSpecified = (defaultProfile.isAutomationShell ? shellLaunchConfig.args : shellOptions?.args) !== undefined; if (shellLaunchConfig.args === undefined) { shellLaunchConfig.args = []; } @@ -1138,34 +1139,29 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { windowsShellArgs = true; // If we don't have a cwd, then the terminal uses the home dir. const userHome = await this._pathService.userHome(); - if (basename === 'cmd.exe') { - if ((options.cwd && isUNC(options.cwd)) || (!options.cwd && isUNC(userHome.fsPath))) { - return undefined; - } - if (!taskShellArgsSpecified) { - toAdd.push('/d', '/c'); - } - } else if ((basename === 'powershell.exe') || (basename === 'pwsh.exe')) { - if (!taskShellArgsSpecified) { + if (basename === 'cmd.exe' && ((options.cwd && isUNC(options.cwd)) || (!options.cwd && isUNC(userHome.fsPath)))) { + return undefined; + } + if ((basename === 'powershell.exe') || (basename === 'pwsh.exe')) { + if (!shellSpecified) { toAdd.push('-Command'); } } else if ((basename === 'bash.exe') || (basename === 'zsh.exe')) { windowsShellArgs = false; - if (!taskShellArgsSpecified) { + if (!shellSpecified) { toAdd.push('-c'); } } else if (basename === 'wsl.exe') { - if (!taskShellArgsSpecified) { + if (!shellSpecified) { toAdd.push('-e'); } } else { - if (!taskShellArgsSpecified) { - // Push `-c` for unknown shells if the user didn't specify the args - toAdd.push('-c'); + if (!shellSpecified) { + toAdd.push('/d', '/c'); } } } else { - if (!taskShellArgsSpecified) { + if (!shellSpecified) { // Under Mac remove -l to not start it as a login shell. if (platform === Platform.Platform.Mac) { // Background on -l on osx https://github.com/microsoft/vscode/issues/107563 @@ -1272,12 +1268,11 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { const combinedShellArgs: string[] = Objects.deepClone(configuredShellArgs); shellCommandArgs.forEach(element => { const shouldAddShellCommandArg = configuredShellArgs.every((arg, index) => { - const isDuplicated = arg.toLowerCase() === element.toLowerCase(); - if (isDuplicated && (configuredShellArgs.length > index + 1)) { + if ((arg.toLowerCase() === element) && (configuredShellArgs.length > index + 1)) { // We can still add the argument, but only if not all of the following arguments begin with "-". return !configuredShellArgs.slice(index + 1).every(testArg => testArg.startsWith('-')); } else { - return !isDuplicated; + return arg.toLowerCase() !== element; } }); if (shouldAddShellCommandArg) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts index e54a12a1b44d8..6208cfc0413c5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts @@ -268,7 +268,6 @@ export abstract class BaseTerminalProfileResolverService extends Disposable impl const automationProfile = this._configurationService.getValue(`terminal.integrated.automationProfile.${this._getOsKey(options.os)}`); if (this._isValidAutomationProfile(automationProfile, options.os)) { automationProfile.icon = this._getCustomIcon(automationProfile.icon) || Codicon.tools; - automationProfile.isAutomationShell = true; return automationProfile; } From 0f2ee1f669f1e3219be9f8764aafb34208f1f586 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Fri, 20 Dec 2024 10:29:11 -0800 Subject: [PATCH 180/200] fix: promote suggested file to working set when opened (#236737) --- .../contrib/chat/browser/chatEditing/chatEditingSession.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index 31e9a672a1333..8552946891ff1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -265,8 +265,8 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio } if (existingTransientEntries.has(uri)) { existingTransientEntries.delete(uri); - } else if (!this._workingSet.has(uri) && !this._removedTransientEntries.has(uri)) { - // Don't add as a transient entry if it's already part of the working set + } else if ((!this._workingSet.has(uri) || this._workingSet.get(uri)?.state === WorkingSetEntryState.Suggested) && !this._removedTransientEntries.has(uri)) { + // Don't add as a transient entry if it's already a confirmed part of the working set // or if the user has intentionally removed it from the working set activeEditors.add(uri); } From 720422ca070ad31eaafb8ab0e1b89a7d781f5e07 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Fri, 20 Dec 2024 10:29:24 -0800 Subject: [PATCH 181/200] chore: bump milestone in work notebook (#236730) --- .vscode/notebooks/my-work.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index b47a08b5a186f..e8b184f8e573c 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n// current milestone name\n$MILESTONE=milestone:\"November 2024\"\n" + "value": "// list of repos we work in\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n// current milestone name\n$MILESTONE=milestone:\"January 2025\"\n" }, { "kind": 1, From eee5e7643a2481ee1dac9a7e75f922ccc4e40f40 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 20 Dec 2024 10:29:40 -0800 Subject: [PATCH 182/200] cli: propagate server-data-dir and extensions-dir when installing service (#236734) Fixes #236195 --- cli/src/bin/code/main.rs | 4 +-- cli/src/commands/tunnels.rs | 55 ++++++++++++++++++++++++------------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/cli/src/bin/code/main.rs b/cli/src/bin/code/main.rs index 0e4809b78c749..b73d0aa885b04 100644 --- a/cli/src/bin/code/main.rs +++ b/cli/src/bin/code/main.rs @@ -103,7 +103,7 @@ async fn main() -> Result<(), std::convert::Infallible> { serve_web::serve_web(context!(), sw_args).await } - Some(args::Commands::Tunnel(tunnel_args)) => match tunnel_args.subcommand { + Some(args::Commands::Tunnel(mut tunnel_args)) => match tunnel_args.subcommand.take() { Some(args::TunnelSubcommand::Prune) => tunnels::prune(context!()).await, Some(args::TunnelSubcommand::Unregister) => tunnels::unregister(context!()).await, Some(args::TunnelSubcommand::Kill) => tunnels::kill(context!()).await, @@ -116,7 +116,7 @@ async fn main() -> Result<(), std::convert::Infallible> { tunnels::user(context!(), user_command).await } Some(args::TunnelSubcommand::Service(service_args)) => { - tunnels::service(context_no_logger(), service_args).await + tunnels::service(context_no_logger(), tunnel_args, service_args).await } Some(args::TunnelSubcommand::ForwardInternal(forward_args)) => { tunnels::forward(context_no_logger(), forward_args).await diff --git a/cli/src/commands/tunnels.rs b/cli/src/commands/tunnels.rs index 2a0b4c7ddcef6..f52fa714793be 100644 --- a/cli/src/commands/tunnels.rs +++ b/cli/src/commands/tunnels.rs @@ -21,7 +21,7 @@ use tokio::{ use super::{ args::{ - AuthProvider, CliCore, CommandShellArgs, ExistingTunnelArgs, TunnelForwardArgs, + AuthProvider, CliCore, CommandShellArgs, ExistingTunnelArgs, TunnelArgs, TunnelForwardArgs, TunnelRenameArgs, TunnelServeArgs, TunnelServiceSubCommands, TunnelUserSubCommands, }, CommandContext, @@ -104,12 +104,16 @@ fn fulfill_existing_tunnel_args( } struct TunnelServiceContainer { - args: CliCore, + core_args: CliCore, + tunnel_args: TunnelArgs, } impl TunnelServiceContainer { - fn new(args: CliCore) -> Self { - Self { args } + fn new(core_args: CliCore, tunnel_args: TunnelArgs) -> Self { + Self { + core_args, + tunnel_args, + } } } @@ -120,7 +124,8 @@ impl ServiceContainer for TunnelServiceContainer { log: log::Logger, launcher_paths: LauncherPaths, ) -> Result<(), AnyError> { - let csa = (&self.args).into(); + let mut csa = (&self.core_args).into(); + self.tunnel_args.serve_args.server_args.apply_to(&mut csa); serve_with_csa( launcher_paths, log, @@ -242,8 +247,27 @@ async fn is_port_available(host: IpAddr, port: u16) -> bool { .is_ok() } +fn make_service_args<'a: 'c, 'b: 'c, 'c>( + root_path: &'a str, + tunnel_args: &'b TunnelArgs, +) -> Vec<&'c str> { + let mut args = ["--verbose", "--cli-data-dir", root_path, "tunnel"].to_vec(); + + if let Some(d) = tunnel_args.serve_args.server_args.extensions_dir.as_ref() { + args.extend_from_slice(&["--extensions-dir", d]); + } + if let Some(d) = tunnel_args.serve_args.server_args.server_data_dir.as_ref() { + args.extend_from_slice(&["--server-data-dir", d]); + } + + args.extend_from_slice(&["service", "internal-run"]); + + args +} + pub async fn service( ctx: CommandContext, + tunnel_args: TunnelArgs, service_args: TunnelServiceSubCommands, ) -> Result { let manager = create_service_manager(ctx.log.clone(), &ctx.paths); @@ -265,20 +289,10 @@ pub async fn service( legal::require_consent(&ctx.paths, args.accept_server_license_terms)?; let current_exe = canonical_exe().map_err(|e| wrap(e, "could not get current exe"))?; + let root_path = ctx.paths.root().as_os_str().to_string_lossy(); + let args = make_service_args(&root_path, &tunnel_args); - manager - .register( - current_exe, - &[ - "--verbose", - "--cli-data-dir", - ctx.paths.root().as_os_str().to_string_lossy().as_ref(), - "tunnel", - "service", - "internal-run", - ], - ) - .await?; + manager.register(current_exe, &args).await?; ctx.log.result(format!("Service successfully installed! You can use `{APPLICATION_NAME} tunnel service log` to monitor it, and `{APPLICATION_NAME} tunnel service uninstall` to remove it.")); } TunnelServiceSubCommands::Uninstall => { @@ -289,7 +303,10 @@ pub async fn service( } TunnelServiceSubCommands::InternalRun => { manager - .run(ctx.paths.clone(), TunnelServiceContainer::new(ctx.args)) + .run( + ctx.paths.clone(), + TunnelServiceContainer::new(ctx.args, tunnel_args), + ) .await?; } } From 15da1932a92e1ba185931b57be5a07a9f2e1cd08 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 20 Dec 2024 12:36:07 -0600 Subject: [PATCH 183/200] Allow completions to be requested earlier (#236630) --- .../terminal/browser/terminalInstance.ts | 52 ++++++++++- .../browser/terminal.suggest.contribution.ts | 91 +++++++++---------- .../suggest/browser/terminalSuggestAddon.ts | 16 +++- 3 files changed, 107 insertions(+), 52 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index df286dd974ad1..589b1deaf9cac 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -504,8 +504,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Resolve the executable ahead of time if shell integration is enabled, this should not // be done for custom PTYs as that would cause extension Pseudoterminal-based terminals // to hang in resolver extensions + let os: OperatingSystem | undefined; if (!this.shellLaunchConfig.customPtyImplementation && this._terminalConfigurationService.config.shellIntegration?.enabled && !this.shellLaunchConfig.executable) { - const os = await this._processManager.getBackendOS(); + os = await this._processManager.getBackendOS(); const defaultProfile = (await this._terminalProfileResolverService.getDefaultProfile({ remoteAuthority: this.remoteAuthority, os })); this.shellLaunchConfig.executable = defaultProfile.path; this.shellLaunchConfig.args = defaultProfile.args; @@ -521,6 +522,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } + // Resolve the shell type ahead of time to allow features that depend upon it to work + // before the process is actually created (like terminal suggest manual request) + if (os && this.shellLaunchConfig.executable) { + this.setShellType(guessShellTypeFromExecutable(os, this.shellLaunchConfig.executable)); + } + await this._createProcess(); // Re-establish the title after reconnect @@ -2656,3 +2663,46 @@ export class TerminalInstanceColorProvider implements IXtermColorProvider { return theme.getColor(SIDE_BAR_BACKGROUND); } } + +function guessShellTypeFromExecutable(os: OperatingSystem, executable: string): TerminalShellType | undefined { + const exeBasename = path.basename(executable); + const generalShellTypeMap: Map = new Map([ + [GeneralShellType.Julia, /^julia$/], + [GeneralShellType.NuShell, /^nu$/], + [GeneralShellType.PowerShell, /^pwsh(-preview)?|powershell$/], + [GeneralShellType.Python, /^py(?:thon)?$/] + ]); + for (const [shellType, pattern] of generalShellTypeMap) { + if (exeBasename.match(pattern)) { + return shellType; + } + } + + if (os === OperatingSystem.Windows) { + const windowsShellTypeMap: Map = new Map([ + [WindowsShellType.CommandPrompt, /^cmd$/], + [WindowsShellType.GitBash, /^bash$/], + [WindowsShellType.Wsl, /^wsl$/] + ]); + for (const [shellType, pattern] of windowsShellTypeMap) { + if (exeBasename.match(pattern)) { + return shellType; + } + } + } else { + const posixShellTypes: PosixShellType[] = [ + PosixShellType.Bash, + PosixShellType.Csh, + PosixShellType.Fish, + PosixShellType.Ksh, + PosixShellType.Sh, + PosixShellType.Zsh, + ]; + for (const type of posixShellTypes) { + if (exeBasename === type) { + return type; + } + } + } + return undefined; +} diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts index 35a845c1f7b85..473de7f5f08aa 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts @@ -12,14 +12,13 @@ import { DisposableStore, MutableDisposable, toDisposable } from '../../../../.. import { isWindows } from '../../../../../base/common/platform.js'; import { localize2 } from '../../../../../nls.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { ContextKeyExpr, IContextKey, IContextKeyService, IReadableSet } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { GeneralShellType, TerminalLocation, TerminalSettingId } from '../../../../../platform/terminal/common/terminal.js'; +import { GeneralShellType, TerminalLocation } from '../../../../../platform/terminal/common/terminal.js'; import { ITerminalContribution, ITerminalInstance, IXtermTerminal } from '../../../terminal/browser/terminal.js'; import { registerActiveInstanceAction } from '../../../terminal/browser/terminalActions.js'; import { registerTerminalContribution, type ITerminalContributionContext } from '../../../terminal/browser/terminalExtensions.js'; -import { TERMINAL_CONFIG_SECTION, type ITerminalConfiguration } from '../../../terminal/common/terminal.js'; import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; import { TerminalSuggestCommandId } from '../common/terminal.suggest.js'; import { terminalSuggestConfigSection, TerminalSuggestSettingId, type ITerminalSuggestConfiguration } from '../common/terminalSuggestConfiguration.js'; @@ -42,8 +41,7 @@ class TerminalSuggestContribution extends DisposableStore implements ITerminalCo private readonly _addon: MutableDisposable = new MutableDisposable(); private readonly _pwshAddon: MutableDisposable = new MutableDisposable(); - private _terminalSuggestWidgetContextKeys: IReadableSet = new Set(TerminalContextKeys.suggestWidgetVisible.key); - private _terminalSuggestWidgetVisibleContextKey: IContextKey; + private readonly _terminalSuggestWidgetVisibleContextKey: IContextKey; get addon(): SuggestAddon | undefined { return this._addon.value; } get pwshAddon(): PwshCompletionProviderAddon | undefined { return this._pwshAddon.value; } @@ -87,23 +85,15 @@ class TerminalSuggestContribution extends DisposableStore implements ITerminalCo if (!enabled) { return; } + this._loadAddons(xterm.raw); this.add(Event.runAndSubscribe(this._ctx.instance.onDidChangeShellType, async () => { - this._loadAddons(xterm.raw); - })); - this.add(this._contextKeyService.onDidChangeContext(e => { - if (e.affectsSome(this._terminalSuggestWidgetContextKeys)) { - this._loadAddons(xterm.raw); - } - })); - this.add(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(TerminalSettingId.SendKeybindingsToShell)) { - this._loadAddons(xterm.raw); - } + this._refreshAddons(); })); } private _loadPwshCompletionAddon(xterm: RawXtermTerminal): void { if (this._ctx.instance.shellType !== GeneralShellType.PowerShell) { + this._pwshAddon.clear(); return; } const pwshCompletionProviderAddon = this._pwshAddon.value = this._instantiationService.createInstance(PwshCompletionProviderAddon, undefined, this._ctx.instance.capabilities); @@ -139,42 +129,47 @@ class TerminalSuggestContribution extends DisposableStore implements ITerminalCo } private _loadAddons(xterm: RawXtermTerminal): void { - const sendingKeybindingsToShell = this._configurationService.getValue(TERMINAL_CONFIG_SECTION).sendKeybindingsToShell; - if (sendingKeybindingsToShell || !this._ctx.instance.shellType) { - this._addon.clear(); - this._pwshAddon.clear(); + // Don't re-create the addon + if (this._addon.value) { return; } - if (this._terminalSuggestWidgetVisibleContextKey) { - const addon = this._addon.value = this._instantiationService.createInstance(SuggestAddon, this._ctx.instance.shellType, this._ctx.instance.capabilities, this._terminalSuggestWidgetVisibleContextKey); - xterm.loadAddon(addon); - this._loadPwshCompletionAddon(xterm); - if (this._ctx.instance.target === TerminalLocation.Editor) { - addon.setContainerWithOverflow(xterm.element!); - } else { - addon.setContainerWithOverflow(dom.findParentWithClass(xterm.element!, 'panel')!); - } - addon.setScreen(xterm.element!.querySelector('.xterm-screen')!); - this.add(this._ctx.instance.onDidBlur(() => addon.hideSuggestWidget())); - this.add(addon.onAcceptedCompletion(async text => { - this._ctx.instance.focus(); - this._ctx.instance.sendText(text, false); - })); - const clipboardContrib = TerminalClipboardContribution.get(this._ctx.instance)!; - this.add(clipboardContrib.onWillPaste(() => addon.isPasting = true)); - this.add(clipboardContrib.onDidPaste(() => { - // Delay this slightly as synchronizing the prompt input is debounced - setTimeout(() => addon.isPasting = false, 100); + + const addon = this._addon.value = this._instantiationService.createInstance(SuggestAddon, this._ctx.instance.shellType, this._ctx.instance.capabilities, this._terminalSuggestWidgetVisibleContextKey); + xterm.loadAddon(addon); + this._loadPwshCompletionAddon(xterm); + if (this._ctx.instance.target === TerminalLocation.Editor) { + addon.setContainerWithOverflow(xterm.element!); + } else { + addon.setContainerWithOverflow(dom.findParentWithClass(xterm.element!, 'panel')!); + } + addon.setScreen(xterm.element!.querySelector('.xterm-screen')!); + this.add(this._ctx.instance.onDidBlur(() => addon.hideSuggestWidget())); + this.add(addon.onAcceptedCompletion(async text => { + this._ctx.instance.focus(); + this._ctx.instance.sendText(text, false); + })); + const clipboardContrib = TerminalClipboardContribution.get(this._ctx.instance)!; + this.add(clipboardContrib.onWillPaste(() => addon.isPasting = true)); + this.add(clipboardContrib.onDidPaste(() => { + // Delay this slightly as synchronizing the prompt input is debounced + setTimeout(() => addon.isPasting = false, 100); + })); + if (!isWindows) { + let barrier: AutoOpenBarrier | undefined; + this.add(addon.onDidReceiveCompletions(() => { + barrier?.open(); + barrier = undefined; })); - if (!isWindows) { - let barrier: AutoOpenBarrier | undefined; - this.add(addon.onDidReceiveCompletions(() => { - barrier?.open(); - barrier = undefined; - })); - } } } + + private _refreshAddons(): void { + const addon = this._addon.value; + if (!addon) { + return; + } + addon.shellType = this._ctx.instance.shellType; + } } registerTerminalContribution(TerminalSuggestContribution.ID, TerminalSuggestContribution); @@ -190,7 +185,7 @@ registerActiveInstanceAction({ primary: KeyMod.CtrlCmd | KeyCode.Space, mac: { primary: KeyMod.WinCtrl | KeyCode.Space }, weight: KeybindingWeight.WorkbenchContrib + 1, - when: ContextKeyExpr.and(TerminalContextKeys.focus, TerminalContextKeys.terminalShellIntegrationEnabled, ContextKeyExpr.equals(`config.${TerminalSuggestSettingId.Enabled}`, true)) + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(`config.${TerminalSuggestSettingId.Enabled}`, true)) }, run: (activeInstance) => TerminalSuggestContribution.get(activeInstance)?.addon?.requestCompletions(true) }); diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index 94650c7525c00..f750d76f089ad 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -73,6 +73,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest private _cancellationTokenSource: CancellationTokenSource | undefined; isPasting: boolean = false; + shellType: TerminalShellType | undefined; private readonly _onBell = this._register(new Emitter()); readonly onBell = this._onBell.event; @@ -89,8 +90,10 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest [TerminalCompletionItemKind.Argument, Codicon.symbolVariable] ]); + private _shouldSyncWhenReady: boolean = false; + constructor( - private readonly _shellType: TerminalShellType | undefined, + shellType: TerminalShellType | undefined, private readonly _capabilities: ITerminalCapabilityStore, private readonly _terminalSuggestWidgetVisibleContextKey: IContextKey, @ITerminalCompletionService private readonly _terminalCompletionService: ITerminalCompletionService, @@ -101,6 +104,8 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest ) { super(); + this.shellType = shellType; + this._register(Event.runAndSubscribe(Event.any( this._capabilities.onDidAddCapabilityType, this._capabilities.onDidRemoveCapabilityType @@ -113,6 +118,10 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest this._promptInputModel.onDidChangeInput(e => this._sync(e)), this._promptInputModel.onDidFinishInput(() => this.hideSuggestWidget()), ); + if (this._shouldSyncWhenReady) { + this._sync(this._promptInputModel); + this._shouldSyncWhenReady = false; + } } this._register(commandDetection.onCommandExecuted(() => this.hideSuggestWidget())); } else { @@ -140,7 +149,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest return; } - if (!this._shellType) { + if (!this.shellType) { return; } @@ -156,7 +165,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest await this._extensionService.activateByEvent('onTerminalCompletionsRequested'); } - const providedCompletions = await this._terminalCompletionService.provideCompletions(this._promptInputModel.prefix, this._promptInputModel.cursorIndex, this._shellType, token, doNotRequestExtensionCompletions); + const providedCompletions = await this._terminalCompletionService.provideCompletions(this._promptInputModel.prefix, this._promptInputModel.cursorIndex, this.shellType, token, doNotRequestExtensionCompletions); if (!providedCompletions?.length || token.isCancellationRequested) { return; } @@ -237,6 +246,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest async requestCompletions(explicitlyInvoked?: boolean): Promise { if (!this._promptInputModel) { + this._shouldSyncWhenReady = true; return; } From 7cf9dbecf611fbc38c006439e704ccc807fa5c0f Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 20 Dec 2024 10:52:07 -0800 Subject: [PATCH 184/200] Fix escaping of raw values that contain `&` in md preview Fixes #236660 --- extensions/markdown-language-features/src/util/dom.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/markdown-language-features/src/util/dom.ts b/extensions/markdown-language-features/src/util/dom.ts index 0f6c00da9daf7..8bbce79c30377 100644 --- a/extensions/markdown-language-features/src/util/dom.ts +++ b/extensions/markdown-language-features/src/util/dom.ts @@ -5,7 +5,10 @@ import * as vscode from 'vscode'; export function escapeAttribute(value: string | vscode.Uri): string { - return value.toString().replace(/"/g, '"'); + return value.toString() + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, '''); } export function getNonce() { From 094e96a2eacd2b0a14af7884c2988b00261f8505 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 20 Dec 2024 19:55:45 +0100 Subject: [PATCH 185/200] Introduces IObservableWithChange so that typescript does not show the default type for TChange in hovers. (#236629) * Introduces IObservableWithChange so that typescript does not show the default type for TChange in hovers. This should make it easier to understand types when observables (potentially nested) are involved. * Fixes monaco editor --- build/monaco/monaco.usage.recipe | 2 +- src/vs/base/common/event.ts | 16 +++++----- .../base/common/observableInternal/autorun.ts | 4 +-- src/vs/base/common/observableInternal/base.ts | 29 ++++++++++++------- .../base/common/observableInternal/derived.ts | 8 ++--- .../base/common/observableInternal/index.ts | 2 +- .../base/common/observableInternal/logging.ts | 8 ++--- .../base/common/observableInternal/utils.ts | 16 +++++----- src/vs/base/test/common/observable.test.ts | 10 +++---- src/vs/editor/browser/observableCodeEditor.ts | 6 ++-- .../diffEditor/components/diffEditorSash.ts | 2 +- .../widget/diffEditor/diffEditorOptions.ts | 4 +-- .../diffEditor/features/gutterFeature.ts | 2 +- .../editor/browser/widget/diffEditor/utils.ts | 4 +-- .../browser/model/inlineCompletionsModel.ts | 4 +-- .../widget/observableCodeEditor.test.ts | 4 +-- .../browser/model/textModelDiffs.ts | 6 ++-- .../chatEdit/notebookChatActionsOverlay.ts | 6 ++-- .../contrib/testing/common/observableUtils.ts | 6 ++-- 19 files changed, 73 insertions(+), 66 deletions(-) diff --git a/build/monaco/monaco.usage.recipe b/build/monaco/monaco.usage.recipe index e3c8cdd09163a..a3369eb25a7d7 100644 --- a/build/monaco/monaco.usage.recipe +++ b/build/monaco/monaco.usage.recipe @@ -35,6 +35,6 @@ import * as editorAPI from './vs/editor/editor.api'; a = editorAPI.editor; a = editorAPI.languages; - const o: IObservable = null!; + const o: IObservable = null!; o.TChange; })(); diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 82921d00dac6c..d49f2ef276f47 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -9,7 +9,7 @@ import { onUnexpectedError } from './errors.js'; import { createSingleCallFunction } from './functional.js'; import { combinedDisposable, Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from './lifecycle.js'; import { LinkedList } from './linkedList.js'; -import { IObservable, IObserver } from './observable.js'; +import { IObservable, IObservableWithChange, IObserver } from './observable.js'; import { StopWatch } from './stopwatch.js'; import { MicrotaskDelay } from './symbols.js'; @@ -666,7 +666,7 @@ export namespace Event { private _counter = 0; private _hasChanged = false; - constructor(readonly _observable: IObservable, store: DisposableStore | undefined) { + constructor(readonly _observable: IObservable, store: DisposableStore | undefined) { const options: EmitterOptions = { onWillAddFirstListener: () => { _observable.addObserver(this); @@ -687,21 +687,21 @@ export namespace Event { } } - beginUpdate(_observable: IObservable): void { + beginUpdate(_observable: IObservable): void { // assert(_observable === this.obs); this._counter++; } - handlePossibleChange(_observable: IObservable): void { + handlePossibleChange(_observable: IObservable): void { // assert(_observable === this.obs); } - handleChange(_observable: IObservable, _change: TChange): void { + handleChange(_observable: IObservableWithChange, _change: TChange): void { // assert(_observable === this.obs); this._hasChanged = true; } - endUpdate(_observable: IObservable): void { + endUpdate(_observable: IObservable): void { // assert(_observable === this.obs); this._counter--; if (this._counter === 0) { @@ -718,7 +718,7 @@ export namespace Event { * Creates an event emitter that is fired when the observable changes. * Each listeners subscribes to the emitter. */ - export function fromObservable(obs: IObservable, store?: DisposableStore): Event { + export function fromObservable(obs: IObservable, store?: DisposableStore): Event { const observer = new EmitterObserver(obs, store); return observer.emitter.event; } @@ -726,7 +726,7 @@ export namespace Event { /** * Each listener is attached to the observable directly. */ - export function fromObservableLight(observable: IObservable): Event { + export function fromObservableLight(observable: IObservable): Event { return (listener, thisArgs, disposables) => { let count = 0; let didChange = false; diff --git a/src/vs/base/common/observableInternal/autorun.ts b/src/vs/base/common/observableInternal/autorun.ts index b8a6262993798..d2425e110121c 100644 --- a/src/vs/base/common/observableInternal/autorun.ts +++ b/src/vs/base/common/observableInternal/autorun.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChangeContext, IObservable, IObserver, IReader } from './base.js'; +import { IChangeContext, IObservable, IObservableWithChange, IObserver, IReader } from './base.js'; import { DebugNameData, IDebugNameData } from './debugName.js'; import { assertFn, BugIndicatingError, DisposableStore, IDisposable, markAsDisposed, onBugIndicatingError, toDisposable, trackDisposable } from './commonFacade/deps.js'; import { getLogger } from './logging.js'; @@ -286,7 +286,7 @@ export class AutorunObserver implements IObserver, IReader } } - public handleChange(observable: IObservable, change: TChange): void { + public handleChange(observable: IObservableWithChange, change: TChange): void { if (this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { try { const shouldReact = this._handleChange ? this._handleChange({ diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index c28e773b26886..f2aa0466f7830 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -9,6 +9,13 @@ import type { derivedOpts } from './derived.js'; import { getLogger, logObservable } from './logging.js'; import { keepObserved, recomputeInitiallyAndOnChange } from './utils.js'; +/** + * Represents an observable value. + * + * @template T The type of the values the observable can hold. + */ +export interface IObservable extends IObservableWithChange { } + /** * Represents an observable value. * @@ -18,7 +25,7 @@ import { keepObserved, recomputeInitiallyAndOnChange } from './utils.js'; * While observers can miss temporary values of an observable, * they will receive all change values (as long as they are subscribed)! */ -export interface IObservable { +export interface IObservableWithChange { /** * Returns the current value. * @@ -71,7 +78,7 @@ export interface IObservable { * ONLY FOR DEBUGGING! * Logs computations of this derived. */ - log(): IObservable; + log(): IObservableWithChange; /** * Makes sure this value is computed eagerly. @@ -98,7 +105,7 @@ export interface IReader { /** * Reads the value of an observable and subscribes to it. */ - readObservable(observable: IObservable): T; + readObservable(observable: IObservableWithChange): T; } /** @@ -143,7 +150,7 @@ export interface IObserver { * * @param change Indicates how or why the value changed. */ - handleChange(observable: IObservable, change: TChange): void; + handleChange(observable: IObservableWithChange, change: TChange): void; } export interface ISettable { @@ -162,7 +169,7 @@ export interface ITransaction { * Calls {@link Observer.beginUpdate} immediately * and {@link Observer.endUpdate} when the transaction ends. */ - updateObserver(observer: IObserver, observable: IObservable): void; + updateObserver(observer: IObserver, observable: IObservableWithChange): void; } let _recomputeInitiallyAndOnChange: typeof recomputeInitiallyAndOnChange; @@ -185,7 +192,7 @@ export function _setDerivedOpts(derived: typeof _derived) { _derived = derived; } -export abstract class ConvenientObservable implements IObservable { +export abstract class ConvenientObservable implements IObservableWithChange { get TChange(): TChange { return null!; } public abstract get(): T; @@ -239,7 +246,7 @@ export abstract class ConvenientObservable implements IObservable { + public log(): IObservableWithChange { logObservable(this); return this; } @@ -248,7 +255,7 @@ export abstract class ConvenientObservable implements IObservable(this: IObservable>): IObservable { + public flatten(this: IObservable>): IObservable { return _derived( { owner: undefined, @@ -390,7 +397,7 @@ export class TransactionImpl implements ITransaction { /** * A settable observable. */ -export interface ISettableObservable extends IObservable, ISettable { +export interface ISettableObservable extends IObservableWithChange, ISettable { } /** @@ -505,11 +512,11 @@ export interface IChangeTracker { } export interface IChangeContext { - readonly changedObservable: IObservable; + readonly changedObservable: IObservableWithChange; readonly change: unknown; /** * Returns if the given observable caused the change. */ - didChange(observable: IObservable): this is { change: TChange }; + didChange(observable: IObservableWithChange): this is { change: TChange }; } diff --git a/src/vs/base/common/observableInternal/derived.ts b/src/vs/base/common/observableInternal/derived.ts index ba018041799d9..5421b023df9fb 100644 --- a/src/vs/base/common/observableInternal/derived.ts +++ b/src/vs/base/common/observableInternal/derived.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BaseObservable, IChangeContext, IObservable, IObserver, IReader, ISettableObservable, ITransaction, _setDerivedOpts, } from './base.js'; +import { BaseObservable, IChangeContext, IObservable, IObservableWithChange, IObserver, IReader, ISettableObservable, ITransaction, _setDerivedOpts, } from './base.js'; import { DebugNameData, DebugOwner, IDebugNameData } from './debugName.js'; import { BugIndicatingError, DisposableStore, EqualityComparer, IDisposable, assertFn, onBugIndicatingError, strictEquals } from './commonFacade/deps.js'; import { getLogger } from './logging.js'; @@ -386,7 +386,7 @@ export class Derived extends BaseObservable im assertFn(() => this.updateCount >= 0); } - public handlePossibleChange(observable: IObservable): void { + public handlePossibleChange(observable: IObservable): void { // In all other states, observers already know that we might have changed. if (this.state === DerivedState.upToDate && this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { this.state = DerivedState.dependenciesMightHaveChanged; @@ -396,7 +396,7 @@ export class Derived extends BaseObservable im } } - public handleChange(observable: IObservable, change: TChange): void { + public handleChange(observable: IObservableWithChange, change: TChange): void { if (this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { let shouldReact = false; try { @@ -460,7 +460,7 @@ export class Derived extends BaseObservable im super.removeObserver(observer); } - public override log(): IObservable { + public override log(): IObservableWithChange { if (!getLogger()) { super.log(); getLogger()?.handleDerivedCreated(this); diff --git a/src/vs/base/common/observableInternal/index.ts b/src/vs/base/common/observableInternal/index.ts index 9295f2697d503..873cf946171c9 100644 --- a/src/vs/base/common/observableInternal/index.ts +++ b/src/vs/base/common/observableInternal/index.ts @@ -7,7 +7,7 @@ export { observableValueOpts } from './api.js'; export { autorun, autorunDelta, autorunHandleChanges, autorunOpts, autorunWithStore, autorunWithStoreHandleChanges } from './autorun.js'; -export { asyncTransaction, disposableObservableValue, globalTransaction, observableValue, subtransaction, transaction, TransactionImpl, type IChangeContext, type IChangeTracker, type IObservable, type IObserver, type IReader, type ISettable, type ISettableObservable, type ITransaction, } from './base.js'; +export { asyncTransaction, disposableObservableValue, globalTransaction, observableValue, subtransaction, transaction, TransactionImpl, type IChangeContext, type IChangeTracker, type IObservable, type IObservableWithChange, type IObserver, type IReader, type ISettable, type ISettableObservable, type ITransaction, } from './base.js'; export { derived, derivedDisposable, derivedHandleChanges, derivedOpts, derivedWithSetter, derivedWithStore } from './derived.js'; export { ObservableLazy, ObservableLazyPromise, ObservablePromise, PromiseResult, } from './promise.js'; export { derivedWithCancellationToken, waitForState } from './utilsCancellation.js'; diff --git a/src/vs/base/common/observableInternal/logging.ts b/src/vs/base/common/observableInternal/logging.ts index a6a5bb78a0698..8dc526a45be09 100644 --- a/src/vs/base/common/observableInternal/logging.ts +++ b/src/vs/base/common/observableInternal/logging.ts @@ -39,7 +39,7 @@ interface IChangeInformation { } export interface IObservableLogger { - handleObservableChanged(observable: IObservable, info: IChangeInformation): void; + handleObservableChanged(observable: IObservable, info: IChangeInformation): void; handleFromEventObservableTriggered(observable: FromEventObservable, info: IChangeInformation): void; handleAutorunCreated(autorun: AutorunObserver): void; @@ -101,7 +101,7 @@ export class ConsoleObservableLogger implements IObservableLogger { : [normalText(` (unchanged)`)]; } - handleObservableChanged(observable: IObservable, info: IChangeInformation): void { + handleObservableChanged(observable: IObservable, info: IChangeInformation): void { if (!this._isIncluded(observable)) { return; } console.log(...this.textToConsoleArgs([ formatKind('observable value changed'), @@ -110,9 +110,9 @@ export class ConsoleObservableLogger implements IObservableLogger { ])); } - private readonly changedObservablesSets = new WeakMap>>(); + private readonly changedObservablesSets = new WeakMap>>(); - formatChanges(changes: Set>): ConsoleText | undefined { + formatChanges(changes: Set>): ConsoleText | undefined { if (changes.size === 0) { return undefined; } diff --git a/src/vs/base/common/observableInternal/utils.ts b/src/vs/base/common/observableInternal/utils.ts index 2fb57d1a42ae3..c42f12f7b8ef7 100644 --- a/src/vs/base/common/observableInternal/utils.ts +++ b/src/vs/base/common/observableInternal/utils.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { autorun, autorunOpts, autorunWithStoreHandleChanges } from './autorun.js'; -import { BaseObservable, ConvenientObservable, IObservable, IObserver, IReader, ITransaction, _setKeepObserved, _setRecomputeInitiallyAndOnChange, observableValue, subtransaction, transaction } from './base.js'; +import { BaseObservable, ConvenientObservable, IObservable, IObservableWithChange, IObserver, IReader, ITransaction, _setKeepObserved, _setRecomputeInitiallyAndOnChange, observableValue, subtransaction, transaction } from './base.js'; import { DebugNameData, DebugOwner, IDebugNameData, getDebugName, } from './debugName.js'; import { BugIndicatingError, DisposableStore, EqualityComparer, Event, IDisposable, IValueWithChangeEvent, strictEquals, toDisposable } from './commonFacade/deps.js'; import { derived, derivedOpts } from './derived.js'; @@ -259,7 +259,7 @@ export function observableSignal(debugNameOrOwner: string | objec } } -export interface IObservableSignal extends IObservable { +export interface IObservableSignal extends IObservableWithChange { trigger(tx: ITransaction | undefined, change: TChange): void; } @@ -434,11 +434,11 @@ export class KeepAliveObserver implements IObserver { private readonly _handleValue: ((value: any) => void) | undefined, ) { } - beginUpdate(observable: IObservable): void { + beginUpdate(observable: IObservable): void { this._counter++; } - endUpdate(observable: IObservable): void { + endUpdate(observable: IObservable): void { this._counter--; if (this._counter === 0 && this._forceRecompute) { if (this._handleValue) { @@ -449,11 +449,11 @@ export class KeepAliveObserver implements IObserver { } } - handlePossibleChange(observable: IObservable): void { + handlePossibleChange(observable: IObservable): void { // NO OP } - handleChange(observable: IObservable, change: TChange): void { + handleChange(observable: IObservableWithChange, change: TChange): void { // NO OP } } @@ -625,7 +625,7 @@ export function derivedConstOnceDefined(owner: DebugOwner, fn: (reader: IRead type RemoveUndefined = T extends undefined ? never : T; -export function runOnChange(observable: IObservable, cb: (value: T, previousValue: undefined | T, deltas: RemoveUndefined[]) => void): IDisposable { +export function runOnChange(observable: IObservableWithChange, cb: (value: T, previousValue: undefined | T, deltas: RemoveUndefined[]) => void): IDisposable { let _previousValue: T | undefined; return autorunWithStoreHandleChanges({ createEmptyChangeSummary: () => ({ deltas: [] as RemoveUndefined[], didChange: false }), @@ -649,7 +649,7 @@ export function runOnChange(observable: IObservable, cb: }); } -export function runOnChangeWithStore(observable: IObservable, cb: (value: T, previousValue: undefined | T, deltas: RemoveUndefined[], store: DisposableStore) => void): IDisposable { +export function runOnChangeWithStore(observable: IObservableWithChange, cb: (value: T, previousValue: undefined | T, deltas: RemoveUndefined[], store: DisposableStore) => void): IDisposable { const store = new DisposableStore(); const disposable = runOnChange(observable, (value, previousValue: undefined | T, deltas) => { store.clear(); diff --git a/src/vs/base/test/common/observable.test.ts b/src/vs/base/test/common/observable.test.ts index 3d7ed472fcdb1..5d33ac30a9c19 100644 --- a/src/vs/base/test/common/observable.test.ts +++ b/src/vs/base/test/common/observable.test.ts @@ -8,7 +8,7 @@ import { setUnexpectedErrorHandler } from '../../common/errors.js'; import { Emitter, Event } from '../../common/event.js'; import { DisposableStore } from '../../common/lifecycle.js'; import { autorun, autorunHandleChanges, derived, derivedDisposable, IObservable, IObserver, ISettableObservable, ITransaction, keepObserved, observableFromEvent, observableSignal, observableValue, transaction, waitForState } from '../../common/observable.js'; -import { BaseObservable } from '../../common/observableInternal/base.js'; +import { BaseObservable, IObservableWithChange } from '../../common/observableInternal/base.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; suite('observables', () => { @@ -1486,18 +1486,18 @@ export class LoggingObserver implements IObserver { constructor(public readonly debugName: string, private readonly log: Log) { } - beginUpdate(observable: IObservable): void { + beginUpdate(observable: IObservable): void { this.count++; this.log.log(`${this.debugName}.beginUpdate (count ${this.count})`); } - endUpdate(observable: IObservable): void { + endUpdate(observable: IObservable): void { this.log.log(`${this.debugName}.endUpdate (count ${this.count})`); this.count--; } - handleChange(observable: IObservable, change: TChange): void { + handleChange(observable: IObservableWithChange, change: TChange): void { this.log.log(`${this.debugName}.handleChange (count ${this.count})`); } - handlePossibleChange(observable: IObservable): void { + handlePossibleChange(observable: IObservable): void { this.log.log(`${this.debugName}.handlePossibleChange`); } } diff --git a/src/vs/editor/browser/observableCodeEditor.ts b/src/vs/editor/browser/observableCodeEditor.ts index 3633d4cac45ca..ac1ab182fa446 100644 --- a/src/vs/editor/browser/observableCodeEditor.ts +++ b/src/vs/editor/browser/observableCodeEditor.ts @@ -5,7 +5,7 @@ import { equalsIfDefined, itemsEquals } from '../../base/common/equals.js'; import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../base/common/lifecycle.js'; -import { IObservable, ITransaction, TransactionImpl, autorun, autorunOpts, derived, derivedOpts, derivedWithSetter, observableFromEvent, observableSignal, observableValue, observableValueOpts } from '../../base/common/observable.js'; +import { IObservable, IObservableWithChange, ITransaction, TransactionImpl, autorun, autorunOpts, derived, derivedOpts, derivedWithSetter, observableFromEvent, observableSignal, observableValue, observableValueOpts } from '../../base/common/observable.js'; import { EditorOption, FindComputedEditorOptionValueById } from '../common/config/editorOptions.js'; import { LineRange } from '../common/core/lineRange.js'; import { OffsetRange } from '../common/core/offsetRange.js'; @@ -155,13 +155,13 @@ export class ObservableCodeEditor extends Disposable { public readonly isReadonly = observableFromEvent(this, this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.readOnly)); private readonly _versionId = observableValueOpts({ owner: this, lazy: true }, this.editor.getModel()?.getVersionId() ?? null); - public readonly versionId: IObservable = this._versionId; + public readonly versionId: IObservableWithChange = this._versionId; private readonly _selections = observableValueOpts( { owner: this, equalsFn: equalsIfDefined(itemsEquals(Selection.selectionsEqual)), lazy: true }, this.editor.getSelections() ?? null ); - public readonly selections: IObservable = this._selections; + public readonly selections: IObservableWithChange = this._selections; public readonly positions = derivedOpts( diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorSash.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorSash.ts index 8abaddcceb95b..468e6323c7f60 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorSash.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorSash.ts @@ -62,7 +62,7 @@ export class DiffEditorSash extends Disposable { private readonly _domNode: HTMLElement, private readonly _dimensions: { height: IObservable; width: IObservable }, private readonly _enabled: IObservable, - private readonly _boundarySashes: IObservable, + private readonly _boundarySashes: IObservable, public readonly sashLeft: ISettableObservable, private readonly _resetSash: () => void, ) { diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts index 0c1a533681f36..3d2e483ca1543 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IObservable, ISettableObservable, derived, derivedConstOnceDefined, observableFromEvent, observableValue } from '../../../../base/common/observable.js'; +import { IObservable, IObservableWithChange, ISettableObservable, derived, derivedConstOnceDefined, observableFromEvent, observableValue } from '../../../../base/common/observable.js'; import { Constants } from '../../../../base/common/uint.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; import { diffEditorDefaultOptions } from '../../../common/config/diffEditor.js'; @@ -15,7 +15,7 @@ import { DiffEditorViewModel, DiffState } from './diffEditorViewModel.js'; export class DiffEditorOptions { private readonly _options: ISettableObservable, { changedOptions: IDiffEditorOptions }>; - public get editorOptions(): IObservable { return this._options; } + public get editorOptions(): IObservableWithChange { return this._options; } private readonly _diffEditorWidth = observableValue(this, 0); diff --git a/src/vs/editor/browser/widget/diffEditor/features/gutterFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/gutterFeature.ts index 556378aebf3b8..17ff41896b412 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/gutterFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/gutterFeature.ts @@ -50,7 +50,7 @@ export class DiffEditorGutter extends Disposable { private readonly _editors: DiffEditorEditors, private readonly _options: DiffEditorOptions, private readonly _sashLayout: SashLayout, - private readonly _boundarySashes: IObservable, + private readonly _boundarySashes: IObservable, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IMenuService private readonly _menuService: IMenuService, diff --git a/src/vs/editor/browser/widget/diffEditor/utils.ts b/src/vs/editor/browser/widget/diffEditor/utils.ts index d34d60fd45748..47faca2f8cf2a 100644 --- a/src/vs/editor/browser/widget/diffEditor/utils.ts +++ b/src/vs/editor/browser/widget/diffEditor/utils.ts @@ -7,7 +7,7 @@ import { IDimension } from '../../../../base/browser/dom.js'; import { findLast } from '../../../../base/common/arraysFind.js'; import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { Disposable, DisposableStore, IDisposable, IReference, toDisposable } from '../../../../base/common/lifecycle.js'; -import { IObservable, ISettableObservable, autorun, autorunHandleChanges, autorunOpts, autorunWithStore, observableValue, transaction } from '../../../../base/common/observable.js'; +import { IObservable, IObservableWithChange, ISettableObservable, autorun, autorunHandleChanges, autorunOpts, autorunWithStore, observableValue, transaction } from '../../../../base/common/observable.js'; import { ElementSizeObserver } from '../../config/elementSizeObserver.js'; import { ICodeEditor, IOverlayWidget, IViewZone } from '../../editorBrowser.js'; import { Position } from '../../../common/core/position.js'; @@ -126,7 +126,7 @@ export class ObservableElementSizeObserver extends Disposable { } } -export function animatedObservable(targetWindow: Window, base: IObservable, store: DisposableStore): IObservable { +export function animatedObservable(targetWindow: Window, base: IObservableWithChange, store: DisposableStore): IObservable { let targetVal = base.get(); let startVal = targetVal; let curVal = targetVal; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index a15005e75fa85..b77a2c0df81d0 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -7,7 +7,7 @@ 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 { IObservable, IObservableWithChange, 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'; @@ -63,7 +63,7 @@ export class InlineCompletionsModel extends Disposable { constructor( public readonly textModel: ITextModel, private readonly _selectedSuggestItem: IObservable, - public readonly _textModelVersionId: IObservable, + public readonly _textModelVersionId: IObservableWithChange, private readonly _positions: IObservable, private readonly _debounceValue: IFeatureDebounceInformation, private readonly _enabled: IObservable, diff --git a/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts b/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts index d9144896afeee..e8406d868f87a 100644 --- a/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts +++ b/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts @@ -112,7 +112,7 @@ suite("CodeEditorWidget", () => { })); test("listener interaction (unforced)", () => { - let derived: IObservable; + let derived: IObservable; let log: Log; withEditorSetupTestFixture( (editor, disposables) => { @@ -143,7 +143,7 @@ suite("CodeEditorWidget", () => { }); test("listener interaction ()", () => { - let derived: IObservable; + let derived: IObservable; let log: Log; withEditorSetupTestFixture( (editor, disposables) => { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts index 63512ae0e383d..2741b6681d497 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts @@ -12,7 +12,7 @@ import { LineRangeEdit } from './editing.js'; import { LineRange } from './lineRange.js'; import { ReentrancyBarrier } from '../../../../../base/common/controlFlow.js'; import { IMergeDiffComputer } from './diffComputer.js'; -import { autorun, IObservable, IReader, ITransaction, observableSignal, observableValue, transaction } from '../../../../../base/common/observable.js'; +import { autorun, IObservableWithChange, IReader, ITransaction, observableSignal, observableValue, transaction } from '../../../../../base/common/observable.js'; import { UndoRedoGroup } from '../../../../../platform/undoRedo/common/undoRedo.js'; export class TextModelDiffs extends Disposable { @@ -61,14 +61,14 @@ export class TextModelDiffs extends Disposable { })); } - public get state(): IObservable { + public get state(): IObservableWithChange { return this._state; } /** * Diffs from base to input. */ - public get diffs(): IObservable { + public get diffs(): IObservableWithChange { return this._diffs; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts index 552a657df4d3c..7ac61fbcb3034 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts @@ -24,7 +24,7 @@ import { navigationBearingFakeActionId } from '../../../../chat/browser/chatEdit export class NotebookChatActionsOverlayController extends Disposable { constructor( private readonly notebookEditor: INotebookEditor, - cellDiffInfo: IObservable, + cellDiffInfo: IObservable, deletedCellDecorator: INotebookDeletedCellDecorator, @IChatEditingService private readonly _chatEditingService: IChatEditingService, @IInstantiationService instantiationService: IInstantiationService, @@ -60,7 +60,7 @@ export class NotebookChatActionsOverlay extends Disposable { constructor( private readonly notebookEditor: INotebookEditor, entry: IModifiedFileEntry, - cellDiffInfo: IObservable, + cellDiffInfo: IObservable, nextEntry: IModifiedFileEntry, previousEntry: IModifiedFileEntry, deletedCellDecorator: INotebookDeletedCellDecorator, @@ -196,7 +196,7 @@ export class NotebookChatActionsOverlay extends Disposable { class NextPreviousChangeActionRunner extends ActionRunner { constructor( private readonly notebookEditor: INotebookEditor, - private readonly cellDiffInfo: IObservable, + private readonly cellDiffInfo: IObservable, private readonly entry: IModifiedFileEntry, private readonly next: IModifiedFileEntry, private readonly direction: 'next' | 'previous', diff --git a/src/vs/workbench/contrib/testing/common/observableUtils.ts b/src/vs/workbench/contrib/testing/common/observableUtils.ts index 3153f2e5bd8a6..9294c905002ef 100644 --- a/src/vs/workbench/contrib/testing/common/observableUtils.ts +++ b/src/vs/workbench/contrib/testing/common/observableUtils.ts @@ -4,16 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable } from '../../../../base/common/lifecycle.js'; -import { IObservable, IObserver } from '../../../../base/common/observable.js'; +import { IObservableWithChange, IObserver } from '../../../../base/common/observable.js'; -export function onObservableChange(observable: IObservable, callback: (value: T) => void): IDisposable { +export function onObservableChange(observable: IObservableWithChange, callback: (value: T) => void): IDisposable { const o: IObserver = { beginUpdate() { }, endUpdate() { }, handlePossibleChange(observable) { observable.reportChanges(); }, - handleChange(_observable: IObservable, change: TChange) { + handleChange(_observable: IObservableWithChange, change: TChange) { callback(change as any as T); } }; From 83c336d7d606a8d2c6f50c403a7fecc770e20cbe Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 20 Dec 2024 20:01:59 +0100 Subject: [PATCH 186/200] Log when a derived gets cleared (#236739) --- src/vs/base/common/observableInternal/derived.ts | 1 + src/vs/base/common/observableInternal/logging.ts | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/vs/base/common/observableInternal/derived.ts b/src/vs/base/common/observableInternal/derived.ts index 5421b023df9fb..54a1e99296dbf 100644 --- a/src/vs/base/common/observableInternal/derived.ts +++ b/src/vs/base/common/observableInternal/derived.ts @@ -220,6 +220,7 @@ export class Derived extends BaseObservable im */ this.state = DerivedState.initial; this.value = undefined; + getLogger()?.handleDerivedCleared(this); for (const d of this.dependencies) { d.removeObserver(this); } diff --git a/src/vs/base/common/observableInternal/logging.ts b/src/vs/base/common/observableInternal/logging.ts index 8dc526a45be09..f0bc82170d8ed 100644 --- a/src/vs/base/common/observableInternal/logging.ts +++ b/src/vs/base/common/observableInternal/logging.ts @@ -48,6 +48,7 @@ export interface IObservableLogger { handleDerivedCreated(observable: Derived): void; handleDerivedRecomputed(observable: Derived, info: IChangeInformation): void; + handleDerivedCleared(observable: Derived): void; handleBeginTransaction(transaction: TransactionImpl): void; handleEndTransaction(): void; @@ -170,6 +171,15 @@ export class ConsoleObservableLogger implements IObservableLogger { changedObservables.clear(); } + handleDerivedCleared(derived: Derived): void { + if (!this._isIncluded(derived)) { return; } + + console.log(...this.textToConsoleArgs([ + formatKind('derived cleared'), + styled(derived.debugName, { color: 'BlueViolet' }), + ])); + } + handleFromEventObservableTriggered(observable: FromEventObservable, info: IChangeInformation): void { if (!this._isIncluded(observable)) { return; } From b9f07f1e9478a470353503582e8f5192a84a65e9 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 20 Dec 2024 20:02:18 +0100 Subject: [PATCH 187/200] Adds color descriptions. (#236741) --- .../view/inlineEdits/gutterIndicatorView.ts | 40 +++++++++++++++---- .../browser/view/inlineEdits/indicatorView.ts | 9 ++--- .../view/inlineEdits/sideBySideDiff.ts | 18 ++++----- .../view/inlineEdits/wordReplacementView.ts | 4 +- 4 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts index 739f12b4feb27..a3d7dcddbc10f 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts @@ -17,15 +17,39 @@ import { OffsetRange } from '../../../../../common/core/offsetRange.js'; import { StickyScrollController } from '../../../../stickyScroll/browser/stickyScrollController.js'; import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js'; import { mapOutFalsy, n, rectToProps } from './utils.js'; +import { localize } from '../../../../../../nls.js'; +export const inlineEditIndicatorPrimaryForeground = registerColor( + 'inlineEdit.gutterIndicator.primaryForeground', + buttonForeground, + localize('inlineEdit.gutterIndicator.primaryForeground', 'Foreground color for the primary inline edit gutter indicator.') +); +export const inlineEditIndicatorPrimaryBackground = registerColor( + 'inlineEdit.gutterIndicator.primaryBackground', + buttonBackground, + localize('inlineEdit.gutterIndicator.primaryBackground', 'Background color for the primary inline edit gutter indicator.') +); -export const inlineEditIndicatorPrimaryForeground = registerColor('inlineEdit.gutterIndicator.primaryForeground', buttonForeground, 'Foreground color for the primary inline edit gutter indicator.'); -export const inlineEditIndicatorPrimaryBackground = registerColor('inlineEdit.gutterIndicator.primaryBackground', buttonBackground, 'Background color for the primary inline edit gutter indicator.'); - -export const inlineEditIndicatorSecondaryForeground = registerColor('inlineEdit.gutterIndicator.secondaryForeground', buttonSecondaryForeground, 'Foreground color for the secondary inline edit gutter indicator.'); -export const inlineEditIndicatorSecondaryBackground = registerColor('inlineEdit.gutterIndicator.secondaryBackground', buttonSecondaryBackground, 'Background color for the secondary inline edit gutter indicator.'); +export const inlineEditIndicatorSecondaryForeground = registerColor( + 'inlineEdit.gutterIndicator.secondaryForeground', + buttonSecondaryForeground, + localize('inlineEdit.gutterIndicator.secondaryForeground', 'Foreground color for the secondary inline edit gutter indicator.') +); +export const inlineEditIndicatorSecondaryBackground = registerColor( + 'inlineEdit.gutterIndicator.secondaryBackground', + buttonSecondaryBackground, + localize('inlineEdit.gutterIndicator.secondaryBackground', 'Background color for the secondary inline edit gutter indicator.') +); -export const inlineEditIndicatorsuccessfulForeground = registerColor('inlineEdit.gutterIndicator.successfulForeground', buttonForeground, 'Foreground color for the successful inline edit gutter indicator.'); -export const inlineEditIndicatorsuccessfulBackground = registerColor('inlineEdit.gutterIndicator.successfulBackground', { light: '#2e825c', dark: '#2e825c', hcLight: '#2e825c', hcDark: '#2e825c' }, 'Background color for the successful inline edit gutter indicator.'); +export const inlineEditIndicatorsuccessfulForeground = registerColor( + 'inlineEdit.gutterIndicator.successfulForeground', + buttonForeground, + localize('inlineEdit.gutterIndicator.successfulForeground', 'Foreground color for the successful inline edit gutter indicator.') +); +export const inlineEditIndicatorsuccessfulBackground = registerColor( + 'inlineEdit.gutterIndicator.successfulBackground', + { light: '#2e825c', dark: '#2e825c', hcLight: '#2e825c', hcDark: '#2e825c' }, + localize('inlineEdit.gutterIndicator.successfulBackground', 'Background color for the successful inline edit gutter indicator.') +); export const inlineEditIndicatorBackground = registerColor( 'inlineEdit.gutterIndicator.background', @@ -35,7 +59,7 @@ export const inlineEditIndicatorBackground = registerColor( dark: transparent('tab.inactiveBackground', 0.5), light: '#5f5f5f18', }, - 'Background color for the inline edit gutter indicator.' + localize('inlineEdit.gutterIndicator.background', 'Background color for the inline edit gutter indicator.') ); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/indicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/indicatorView.ts index f584566365836..00b129bbbfb4d 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/indicatorView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/indicatorView.ts @@ -13,16 +13,15 @@ import { registerColor } from '../../../../../../platform/theme/common/colorUtil import { ObservableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; import { OffsetRange } from '../../../../../common/core/offsetRange.js'; import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js'; +import { localize } from '../../../../../../nls.js'; export interface IInlineEditsIndicatorState { editTop: number; showAlways: boolean; } - - -export const inlineEditIndicatorForeground = registerColor('inlineEdit.indicator.foreground', buttonForeground, ''); -export const inlineEditIndicatorBackground = registerColor('inlineEdit.indicator.background', buttonBackground, ''); -export const inlineEditIndicatorBorder = registerColor('inlineEdit.indicator.border', buttonSeparator, ''); +export const inlineEditIndicatorForeground = registerColor('inlineEdit.indicator.foreground', buttonForeground, localize('inlineEdit.indicator.foreground', 'Foreground color for the inline edit indicator.')); +export const inlineEditIndicatorBackground = registerColor('inlineEdit.indicator.background', buttonBackground, localize('inlineEdit.indicator.background', 'Background color for the inline edit indicator.')); +export const inlineEditIndicatorBorder = registerColor('inlineEdit.indicator.border', buttonSeparator, localize('inlineEdit.indicator.border', 'Border color for the inline edit indicator.')); export class InlineEditsIndicator extends Disposable { private readonly _indicator = h('div.inline-edits-view-indicator', { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts index 042dc653cf90a..7198e4870e92c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts @@ -32,48 +32,48 @@ import { InlineCompletionContextKeys } from '../../controller/inlineCompletionCo import { CustomizedMenuWorkbenchToolBar } from '../../hintsWidget/inlineCompletionsHintsWidget.js'; import { PathBuilder, StatusBarViewItem, getOffsetForPos, mapOutFalsy, maxContentWidthInRange, n } from './utils.js'; import { InlineEditWithChanges } from './viewAndDiffProducer.js'; +import { localize } from '../../../../../../nls.js'; export const originalBackgroundColor = registerColor( 'inlineEdit.originalBackground', Color.transparent, - '', + localize('inlineEdit.originalBackground', 'Background color for the original text in inline edits.'), true ); export const modifiedBackgroundColor = registerColor( 'inlineEdit.modifiedBackground', Color.transparent, - '', + localize('inlineEdit.modifiedBackground', 'Background color for the modified text in inline edits.'), true ); export const originalChangedLineBackgroundColor = registerColor( 'inlineEdit.originalChangedLineBackground', Color.transparent, - '', + localize('inlineEdit.originalChangedLineBackground', 'Background color for the changed lines in the original text of inline edits.'), true ); export const originalChangedTextOverlayColor = registerColor( 'inlineEdit.originalChangedTextBackground', diffRemoved, - '', + localize('inlineEdit.originalChangedTextBackground', 'Overlay color for the changed text in the original text of inline edits.'), true ); export const modifiedChangedLineBackgroundColor = registerColor( 'inlineEdit.modifiedChangedLineBackground', Color.transparent, - '', + localize('inlineEdit.modifiedChangedLineBackground', 'Background color for the changed lines in the modified text of inline edits.'), true ); export const modifiedChangedTextOverlayColor = registerColor( 'inlineEdit.modifiedChangedTextBackground', diffInserted, - '', + localize('inlineEdit.modifiedChangedTextBackground', 'Overlay color for the changed text in the modified text of inline edits.'), true ); - export const originalBorder = registerColor( 'inlineEdit.originalBorder', { @@ -82,7 +82,7 @@ export const originalBorder = registerColor( hcDark: editorLineHighlightBorder, hcLight: editorLineHighlightBorder }, - '' + localize('inlineEdit.originalBorder', 'Border color for the original text in inline edits.') ); export const modifiedBorder = registerColor( @@ -93,7 +93,7 @@ export const modifiedBorder = registerColor( hcDark: editorLineHighlightBorder, hcLight: editorLineHighlightBorder }, - '' + localize('inlineEdit.modifiedBorder', 'Border color for the modified text in inline edits.') ); export class InlineEditsSideBySideDiff extends Disposable { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/wordReplacementView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/wordReplacementView.ts index 6e6f213d38ae5..19f28075c3365 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/wordReplacementView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/wordReplacementView.ts @@ -19,7 +19,7 @@ import { ILanguageService } from '../../../../../common/languages/language.js'; import { LineTokens } from '../../../../../common/tokens/lineTokens.js'; import { TokenArray } from '../../../../../common/tokens/tokenArray.js'; import { mapOutFalsy, n, rectToProps } from './utils.js'; - +import { localize } from '../../../../../../nls.js'; export const transparentHoverBackground = registerColor( 'inlineEdit.wordReplacementView.background', { @@ -28,7 +28,7 @@ export const transparentHoverBackground = registerColor( hcLight: transparent(editorHoverStatusBarBackground, 0.1), hcDark: transparent(editorHoverStatusBarBackground, 0.1), }, - 'Background color for the inline edit word replacement view.' + localize('inlineEdit.wordReplacementView.background', 'Background color for the inline edit word replacement view.') ); export class WordReplacementView extends Disposable { From 5d5976d10cde46dabc31bf264f637b8f6da413a9 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 20 Dec 2024 11:22:59 -0800 Subject: [PATCH 188/200] fix: macos external terminal not opening in darkwin remotes (#236426) Closes #236425 --- build/gulpfile.reh.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index 4f00317173dd3..e0df76f1c8f2f 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -63,6 +63,9 @@ const serverResourceIncludes = [ 'out-build/vs/base/node/cpuUsage.sh', 'out-build/vs/base/node/ps.sh', + // External Terminal + 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', + // Terminal shell integration 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1', 'out-build/vs/workbench/contrib/terminal/common/scripts/CodeTabExpansion.psm1', From 9ee30e50da91f818e60c1c3ccf156f64c097bff1 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 20 Dec 2024 14:05:31 -0600 Subject: [PATCH 189/200] fix terminal completion issues with `replacementIndex` (#236728) fix replacement index weirdness + more --- .../suggest/browser/terminalSuggestAddon.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index f750d76f089ad..52d7c1b8c37a6 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -64,7 +64,6 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest private _leadingLineContent?: string; private _cursorIndexDelta: number = 0; private _requestedCompletionsIndex: number = 0; - private _providerReplacementIndex: number = 0; private _lastUserData?: string; static lastAcceptedCompletionTimestamp: number = 0; @@ -171,11 +170,6 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest } this._onDidReceiveCompletions.fire(); - // 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; - this._providerReplacementIndex = replacementIndex; this._requestedCompletionsIndex = this._promptInputModel.cursorIndex; this._currentPromptInputState = { @@ -186,7 +180,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest ghostTextIndex: this._promptInputModel.ghostTextIndex }; - this._leadingLineContent = this._currentPromptInputState.prefix.substring(replacementIndex, replacementIndex + this._promptInputModel.cursorIndex + this._cursorIndexDelta); + this._leadingLineContent = this._currentPromptInputState.prefix.substring(0, this._requestedCompletionsIndex + this._cursorIndexDelta); const completions = providedCompletions.flat(); if (!completions?.length) { @@ -339,7 +333,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest if (this._terminalSuggestWidgetVisibleContextKey.get()) { this._cursorIndexDelta = this._currentPromptInputState.cursorIndex - (this._requestedCompletionsIndex); - let normalizedLeadingLineContent = this._currentPromptInputState.value.substring(this._providerReplacementIndex, this._requestedCompletionsIndex + this._cursorIndexDelta); + let normalizedLeadingLineContent = this._currentPromptInputState.value.substring(0, this._requestedCompletionsIndex + this._cursorIndexDelta); if (this._isFilteringDirectories) { normalizedLeadingLineContent = normalizePathSeparator(normalizedLeadingLineContent, this._pathSeparator); } @@ -458,7 +452,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest // 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, currentPromptInputState.cursorIndex); // Right side of replacement text in the same word let rightSideReplacementText = ''; From 3751af286f68efe9a7ef11315ed0bfb06b8b8c14 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 20 Dec 2024 22:25:17 +0100 Subject: [PATCH 190/200] Implements inline edit indicator hover (#236738) --- .../browser/view/inlineCompletionsView.ts | 2 +- .../view/inlineEdits/gutterIndicatorMenu.ts | 152 ++++++++++++ .../view/inlineEdits/gutterIndicatorView.ts | 83 ++++++- .../view/inlineEdits/sideBySideDiff.ts | 4 +- .../browser/view/inlineEdits/utils.ts | 229 +++++++++++------- .../browser/view/inlineEdits/view.css | 21 +- .../browser/view/inlineEdits/view.ts | 4 +- 7 files changed, 395 insertions(+), 100 deletions(-) create mode 100644 src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorMenu.ts diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts index baaf3c08205c7..fa303f3fc6e35 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts @@ -48,7 +48,7 @@ export class InlineCompletionsView extends Disposable { constructor( private readonly _editor: ICodeEditor, private readonly _model: IObservable, - @IInstantiationService private readonly _instantiationService: IInstantiationService + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorMenu.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorMenu.ts new file mode 100644 index 0000000000000..95168505a402c --- /dev/null +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorMenu.ts @@ -0,0 +1,152 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { renderIcon } from '../../../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { KeybindingLabel, unthemedKeybindingLabelOptions } from '../../../../../../base/browser/ui/keybindingLabel/keybindingLabel.js'; +import { Codicon } from '../../../../../../base/common/codicons.js'; +import { ResolvedKeybinding } from '../../../../../../base/common/keybindings.js'; +import { IObservable, autorun, constObservable, derived, derivedWithStore, observableFromEvent, observableValue } from '../../../../../../base/common/observable.js'; +import { OS } from '../../../../../../base/common/platform.js'; +import { ThemeIcon } from '../../../../../../base/common/themables.js'; +import { localize } from '../../../../../../nls.js'; +import { ICommandService } from '../../../../../../platform/commands/common/commands.js'; +import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; +import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js'; +import { Command } from '../../../../../common/languages.js'; +import { AcceptInlineCompletion, HideInlineCompletion, JumpToNextInlineEdit } from '../../controller/commands.js'; +import { ChildNode, FirstFnArg, LiveElement, n } from './utils.js'; + +export class GutterIndicatorMenuContent { + constructor( + private readonly _selectionOverride: IObservable<'jump' | 'accept' | undefined>, + private readonly _close: (focusEditor: boolean) => void, + private readonly _extensionCommands: IObservable, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, + @ICommandService private readonly _commandService: ICommandService, + ) { + } + + public toDisposableLiveElement(): LiveElement { + return this._createHoverContent().toDisposableLiveElement(); + } + + private _createHoverContent() { + const activeElement = observableValue('active', undefined); + const activeElementOrDefault = derived(reader => this._selectionOverride.read(reader) ?? activeElement.read(reader)); + + const createOptionArgs = (options: { id: string; title: string; icon: ThemeIcon; commandId: string; commandArgs?: unknown[] }): FirstFnArg => { + return { + title: options.title, + icon: options.icon, + keybinding: this._getKeybinding(options.commandArgs ? undefined : options.commandId), + isActive: activeElementOrDefault.map(v => v === options.id), + onHoverChange: v => activeElement.set(v ? options.id : undefined, undefined), + onAction: () => { + this._close(true); + return this._commandService.executeCommand(options.commandId, ...(options.commandArgs ?? [])); + }, + }; + }; + + // TODO make this menu contributable! + return hoverContent([ + // TODO: make header dynamic, get from extension + header(localize('inlineEdit', "Inline Edit")), + option(createOptionArgs({ id: 'jump', title: localize('jump', "Jump"), icon: Codicon.arrowRight, commandId: new JumpToNextInlineEdit().id })), + option(createOptionArgs({ id: 'accept', title: localize('accept', "Accept"), icon: Codicon.check, commandId: new AcceptInlineCompletion().id })), + option(createOptionArgs({ id: 'reject', title: localize('reject', "Reject"), icon: Codicon.close, commandId: new HideInlineCompletion().id })), + separator(), + this._extensionCommands?.map(c => c && c.length > 0 ? [ + ...c.map(c => option(createOptionArgs({ id: c.id, title: c.title, icon: Codicon.symbolEvent, commandId: c.id, commandArgs: c.arguments }))), + separator() + ] : []), + option(createOptionArgs({ id: 'settings', title: localize('settings', "Settings"), icon: Codicon.gear, commandId: 'workbench.action.openSettings', commandArgs: ['inlineSuggest.edits'] })), + ]); + } + + private _getKeybinding(commandId: string | undefined) { + if (!commandId) { + return constObservable(undefined); + } + return observableFromEvent(this._contextKeyService.onDidChangeContext, () => this._keybindingService.lookupKeybinding(commandId, this._contextKeyService, true)); + } +} + +function hoverContent(content: ChildNode) { + return n.div({ + class: 'content', + style: { + margin: 4, + minWidth: 150, + } + }, content); +} + +function header(title: string) { + return n.div({ + class: 'header', + style: { + color: 'var(--vscode-descriptionForeground)', + fontSize: '12px', + fontWeight: '600', + padding: '0 10px', + lineHeight: 26, + } + }, [title]); +} + +function option(props: { + title: string; + icon: ThemeIcon; + keybinding: IObservable; + isActive?: IObservable; + onHoverChange?: (isHovered: boolean) => void; + onAction?: () => void; +}) { + return derivedWithStore((_reader, store) => n.div({ + class: ['monaco-menu-option', props.isActive?.map(v => v && 'active')], + onmouseenter: () => props.onHoverChange?.(true), + onmouseleave: () => props.onHoverChange?.(false), + onclick: props.onAction, + onkeydown: e => { + if (e.key === 'Enter') { + props.onAction?.(); + } + }, + tabIndex: 0, + }, [ + n.elem('span', { + style: { + fontSize: 16, + display: 'flex', + } + }, [renderIcon(props.icon)]), + n.elem('span', {}, [props.title]), + n.div({ + style: { marginLeft: 'auto', opacity: '0.6' }, + ref: elem => { + const keybindingLabel = store.add(new KeybindingLabel(elem, OS, { disableTitle: true, ...unthemedKeybindingLabelOptions })); + store.add(autorun(reader => { + keybindingLabel.set(props.keybinding.read(reader)); + })); + } + }) + ])); +} + +function separator() { + return n.div({ + class: 'menu-separator', + style: { + color: 'var(--vscode-editorActionList-foreground)', + padding: '2px 0', + } + }, n.div({ + style: { + borderBottom: '1px solid var(--vscode-editorHoverWidget-border)', + } + })); +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts index a3d7dcddbc10f..e0a1ce126d0e2 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts @@ -6,16 +6,21 @@ import { renderIcon } from '../../../../../../base/browser/ui/iconLabel/iconLabels.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import { IObservable, constObservable, derived, observableFromEvent } from '../../../../../../base/common/observable.js'; +import { IObservable, autorun, constObservable, derived, observableFromEvent, observableValue } from '../../../../../../base/common/observable.js'; +import { IHoverService } from '../../../../../../platform/hover/browser/hover.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { buttonBackground, buttonForeground, buttonSecondaryBackground, buttonSecondaryForeground } from '../../../../../../platform/theme/common/colorRegistry.js'; import { registerColor, transparent } from '../../../../../../platform/theme/common/colorUtils.js'; import { ObservableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; import { Rect } from '../../../../../browser/rect.js'; +import { HoverService } from '../../../../../browser/services/hoverService/hoverService.js'; +import { HoverWidget } from '../../../../../browser/services/hoverService/hoverWidget.js'; import { EditorOption } from '../../../../../common/config/editorOptions.js'; import { LineRange } from '../../../../../common/core/lineRange.js'; import { OffsetRange } from '../../../../../common/core/offsetRange.js'; import { StickyScrollController } from '../../../../stickyScroll/browser/stickyScrollController.js'; import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js'; +import { GutterIndicatorMenuContent } from './gutterIndicatorMenu.js'; import { mapOutFalsy, n, rectToProps } from './utils.js'; import { localize } from '../../../../../../nls.js'; export const inlineEditIndicatorPrimaryForeground = registerColor( @@ -62,12 +67,14 @@ export const inlineEditIndicatorBackground = registerColor( localize('inlineEdit.gutterIndicator.background', 'Background color for the inline edit gutter indicator.') ); - export class InlineEditsGutterIndicator extends Disposable { constructor( private readonly _editorObs: ObservableCodeEditor, private readonly _originalRange: IObservable, private readonly _model: IObservable, + private readonly _shouldShowHover: IObservable, + @IHoverService private readonly _hoverService: HoverService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(); @@ -77,6 +84,14 @@ export class InlineEditsGutterIndicator extends Disposable { allowEditorOverflow: false, minContentWidthInPx: constObservable(0), })); + + this._register(autorun(reader => { + if (this._shouldShowHover.read(reader)) { + this._showHover(); + } else { + this._hoverService.hideHover(); + } + })); } private readonly _originalRangeObs = mapOutFalsy(this._originalRange); @@ -132,44 +147,88 @@ export class InlineEditsGutterIndicator extends Disposable { private readonly _tabAction = derived(this, reader => { const m = this._model.read(reader); - if (m && m.tabShouldJumpToInlineEdit.read(reader)) { return 'jump' as const; } - if (m && m.tabShouldAcceptInlineEdit.read(reader)) { return 'accept' as const; } + if (this._editorObs.isFocused.read(reader)) { + if (m && m.tabShouldJumpToInlineEdit.read(reader)) { return 'jump' as const; } + if (m && m.tabShouldAcceptInlineEdit.read(reader)) { return 'accept' as const; } + } return 'inactive' as const; }); private readonly _onClickAction = derived(this, reader => { if (this._layout.map(d => d && d.docked).read(reader)) { return { - label: 'Click to accept inline edit', + selectionOverride: 'accept' as const, action: () => { this._model.get()?.accept(); } }; } else { return { - label: 'Click to jump to inline edit', + selectionOverride: 'jump' as const, action: () => { this._model.get()?.jump(); } }; } }); + private readonly _iconRef = n.ref(); + private _hoverVisible: boolean = false; + private readonly _isHoveredOverIcon = observableValue(this, false); + private readonly _hoverSelectionOverride = derived(this, reader => this._isHoveredOverIcon.read(reader) ? this._onClickAction.read(reader).selectionOverride : undefined); + + private _showHover(): void { + if (this._hoverVisible) { + return; + } + + const content = this._instantiationService.createInstance( + GutterIndicatorMenuContent, + this._hoverSelectionOverride, + (focusEditor) => { + h?.dispose(); + if (focusEditor) { + this._editorObs.editor.focus(); + } + }, + this._model.map((m, r) => m?.state.read(r)?.inlineCompletion?.inlineCompletion.source.inlineCompletions.commands), + ).toDisposableLiveElement(); + const h = this._hoverService.showHover({ + target: this._iconRef.element, + content: content.element, + }) as HoverWidget | undefined; + if (h) { + this._hoverVisible = true; + h.onDispose(() => { + content.dispose(); + this._hoverVisible = false; + }); + } else { + content.dispose(); + } + } + private readonly _indicator = n.div({ class: 'inline-edits-view-gutter-indicator', onclick: () => this._onClickAction.get().action(), - title: this._onClickAction.map(a => a.label), style: { position: 'absolute', overflow: 'visible', }, - }, mapOutFalsy(this._layout).map(l => !l ? [] : [ + }, mapOutFalsy(this._layout).map(layout => !layout ? [] : [ n.div({ style: { position: 'absolute', background: 'var(--vscode-inlineEdit-gutterIndicator-background)', borderRadius: '4px', - ...rectToProps(reader => l.read(reader).rect), + ...rectToProps(reader => layout.read(reader).rect), } }), n.div({ class: 'icon', + ref: this._iconRef, + onmouseenter: () => { + // TODO show hover when hovering ghost text etc. + this._isHoveredOverIcon.set(true, undefined); + this._showHover(); + }, + onmouseleave: () => { this._isHoveredOverIcon.set(false, undefined); }, style: { cursor: 'pointer', zIndex: '1000', @@ -192,12 +251,12 @@ export class InlineEditsGutterIndicator extends Disposable { display: 'flex', justifyContent: 'center', transition: 'background-color 0.2s ease-in-out', - ...rectToProps(reader => l.read(reader).iconRect), + ...rectToProps(reader => layout.read(reader).iconRect), } }, [ n.div({ style: { - rotate: l.map(l => { + rotate: layout.map(l => { switch (l.arrowDirection) { case 'right': return '0deg'; case 'bottom': return '90deg'; @@ -207,7 +266,7 @@ export class InlineEditsGutterIndicator extends Disposable { transition: 'rotate 0.2s ease-in-out', } }, [ - renderIcon(Codicon.arrowRight), + renderIcon(Codicon.arrowRight) ]) ]), ])).keepUpdated(this._store); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts index 7198e4870e92c..e1feaa2eb8b78 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts @@ -180,13 +180,15 @@ export class InlineEditsSideBySideDiff extends Disposable { private readonly _editorContainerTopLeft = observableValue | undefined>(this, undefined); private readonly _editorContainer = n.div({ - class: 'editorContainer', + class: ['editorContainer', this._editorObs.getOption(EditorOption.inlineSuggest).map(v => !v.edits.experimental.useGutterIndicator && 'showHover')], style: { position: 'absolute' }, }, [ n.div({ class: 'preview', style: {}, ref: this.previewRef }), n.div({ class: 'toolbar', style: {}, ref: this.toolbarRef }), ]).keepUpdated(this._store); + public readonly isHovered = this._editorContainer.getIsHovered(this._store); + protected readonly _toolbar = this._register(this._instantiationService.createInstance(CustomizedMenuWorkbenchToolBar, this.toolbarRef.element, MenuId.InlineEditsActions, { menuOptions: { renderShortTitle: true }, toolbarOptions: { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts index a70a0721491ab..57e0eb3393e01 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getDomNodePagePosition, h, isSVGElement } from '../../../../../../base/browser/dom.js'; +import { addDisposableListener, getDomNodePagePosition, h, isSVGElement } from '../../../../../../base/browser/dom.js'; import { KeybindingLabel, unthemedKeybindingLabelOptions } from '../../../../../../base/browser/ui/keybindingLabel/keybindingLabel.js'; import { numberComparator } from '../../../../../../base/common/arrays.js'; import { findFirstMin } from '../../../../../../base/common/arraysFind.js'; import { BugIndicatingError } from '../../../../../../base/common/errors.js'; -import { Disposable, DisposableStore, toDisposable } from '../../../../../../base/common/lifecycle.js'; +import { DisposableStore, IDisposable, toDisposable } from '../../../../../../base/common/lifecycle.js'; import { derived, derivedObservableWithCache, IObservable, IReader, observableValue, transaction } from '../../../../../../base/common/observable.js'; import { OS } from '../../../../../../base/common/platform.js'; import { getIndentationLength, splitLines } from '../../../../../../base/common/strings.js'; @@ -164,7 +164,7 @@ export class PathBuilder { } type Value = T | IObservable; -type ValueOrList = Value | Value[]; +type ValueOrList = Value | ValueOrList[]; type ValueOrList2 = ValueOrList | ValueOrList>; type Element = HTMLElement | SVGElement; @@ -203,16 +203,18 @@ type SVGElementTagNameMap2 = { type DomTagCreateFn> = ( tag: TTag, - attributes: ElementAttributeKeys & { class?: Value; ref?: IRef }, - children?: ValueOrList2, + attributes: ElementAttributeKeys & { class?: ValueOrList; ref?: IRef }, + children?: ChildNode, ) => ObserverNode; type DomCreateFn = ( - attributes: ElementAttributeKeys & { class?: Value; ref?: IRef }, - children?: ValueOrList2, + attributes: ElementAttributeKeys & { class?: ValueOrList; ref?: IRef }, + children?: ChildNode, ) => ObserverNode; +export type ChildNode = ValueOrList2; + export namespace n { function nodeNs>(elementNs: string | undefined = undefined): DomTagCreateFn { return (tag, attributes, children) => { @@ -233,35 +235,37 @@ export namespace n { } export const div: DomCreateFn = node('div'); + + export const elem = nodeNs(undefined); + export const svg: DomCreateFn = node('svg', 'http://www.w3.org/2000/svg'); export const svgElem = nodeNs('http://www.w3.org/2000/svg'); - export function ref(): Ref { - return new Ref(); + export function ref(): IRefWithVal { + let value: T | undefined = undefined; + const result: IRef = function (val: T) { + value = val; + }; + Object.defineProperty(result, 'element', { + get() { + if (!value) { + throw new BugIndicatingError('Make sure the ref is set before accessing the element. Maybe wrong initialization order?'); + } + return value; + } + }); + return result as any; } } -export interface IRef { - setValue(value: T): void; -} - -export class Ref implements IRef { - private _value: T | undefined = undefined; - - public setValue(value: T): void { - this._value = value; - } +export type IRef = (value: T) => void; - public get element(): T { - if (!this._value) { - throw new BugIndicatingError('Make sure the ref is set before accessing the element. Maybe wrong initialization order?'); - } - return this._value; - } +export interface IRefWithVal extends IRef { + readonly element: T; } -export abstract class ObserverNode extends Disposable { +export abstract class ObserverNode { private readonly _deriveds: (IObservable)[] = []; protected readonly _element: T; @@ -270,48 +274,23 @@ export abstract class ObserverNode extends Disposab tag: string, ref: IRef | undefined, ns: string | undefined, - className: Value | undefined, + className: ValueOrList | undefined, attributes: ElementAttributeKeys, - children: ValueOrList2 | undefined, + children: ChildNode, ) { - super(); - this._element = (ns ? document.createElementNS(ns, tag) : document.createElement(tag)) as unknown as T; if (ref) { - ref.setValue(this._element); - } - - function setClassName(domNode: Element, className: string | string[]) { - if (isSVGElement(domNode)) { - if (Array.isArray(className)) { - domNode.setAttribute('class', className.join(' ')); - } else { - domNode.setAttribute('class', className); - } - } else { - if (Array.isArray(className)) { - domNode.className = className.join(' '); - } else { - domNode.className = className; - } - } + ref(this._element); } if (className) { - if (isObservable(className)) { + if (hasObservable(className)) { this._deriveds.push(derived(this, reader => { - setClassName(this._element, className.read(reader)); + setClassName(this._element, getClassName(className, reader)); })); } else { - setClassName(this._element, className); - } - } - - function convertCssValue(value: any): string { - if (typeof value === 'number') { - return value + 'px'; + setClassName(this._element, getClassName(className, undefined)); } - return value; } for (const [key, value] of Object.entries(attributes)) { @@ -347,36 +326,26 @@ export abstract class ObserverNode extends Disposab } } - function getChildren(reader: IReader | undefined, children: ValueOrList2): (Element | string)[] { - if (isObservable(children)) { - return getChildren(reader, children.read(reader)); - } - if (Array.isArray(children)) { - return children.flatMap(c => getChildren(reader, c)); - } - if (children instanceof ObserverNode) { - if (reader) { - children.readEffect(reader); + if (children) { + function getChildren(reader: IReader | undefined, children: ValueOrList2): (Element | string)[] { + if (isObservable(children)) { + return getChildren(reader, children.read(reader)); } - return [children._element]; - } - if (children) { - return [children]; - } - return []; - } - - function childrenIsObservable(children: ValueOrList2): boolean { - if (isObservable(children)) { - return true; - } - if (Array.isArray(children)) { - return children.some(c => childrenIsObservable(c)); + if (Array.isArray(children)) { + return children.flatMap(c => getChildren(reader, c)); + } + if (children instanceof ObserverNode) { + if (reader) { + children.readEffect(reader); + } + return [children._element]; + } + if (children) { + return [children]; + } + return []; } - return false; - } - if (children) { const d = derived(this, reader => { this._element.replaceChildren(...getChildren(reader, children)); }); @@ -399,12 +368,102 @@ export abstract class ObserverNode extends Disposab }).recomputeInitiallyAndOnChange(store); return this as unknown as ObserverNodeWithElement; } + + /** + * Creates a live element that will keep the element updated as long as the returned object is not disposed. + */ + toDisposableLiveElement() { + const store = new DisposableStore(); + this.keepUpdated(store); + return new LiveElement(this._element, store); + } +} + + + +function setClassName(domNode: Element, className: string) { + if (isSVGElement(domNode)) { + domNode.setAttribute('class', className); + } else { + domNode.className = className; + } +} + +function resolve(value: ValueOrList, reader: IReader | undefined, cb: (val: T) => void): void { + if (isObservable(value)) { + cb(value.read(reader)); + } + if (Array.isArray(value)) { + for (const v of value) { + resolve(v, reader, cb); + } + } + cb(value as any); +} + +function getClassName(className: ValueOrList | undefined, reader: IReader | undefined): string { + let result = ''; + resolve(className, reader, val => { + if (val) { + if (result.length === 0) { + result = val; + } else { + result += ' ' + val; + } + } + }); + return result; +} + +function hasObservable(value: ValueOrList): boolean { + if (isObservable(value)) { + return true; + } + if (Array.isArray(value)) { + return value.some(v => hasObservable(v)); + } + return false; +} +function convertCssValue(value: any): string { + if (typeof value === 'number') { + return value + 'px'; + } + return value; +} + + +function childrenIsObservable(children: ValueOrList2): boolean { + if (isObservable(children)) { + return true; + } + if (Array.isArray(children)) { + return children.some(c => childrenIsObservable(c)); + } + return false; +} + +export class LiveElement { + constructor( + public readonly element: T, + private readonly _disposable: IDisposable, + ) { } + + dispose() { + this._disposable.dispose(); + } } export class ObserverNodeWithElement extends ObserverNode { public get element() { return this._element; } + + public getIsHovered(store: DisposableStore): IObservable { + const hovered = observableValue('hovered', false); + store.add(addDisposableListener(this._element, 'mouseenter', () => hovered.set(true, undefined))); + store.add(addDisposableListener(this._element, 'mouseleave', () => hovered.set(false, undefined))); + return hovered; + } } function setOrRemoveAttribute(element: Element, key: string, value: unknown) { @@ -479,3 +538,5 @@ export function rectToProps(fn: (reader: IReader) => Rect) { height: derived(reader => fn(reader).bottom - fn(reader).top), }; } + +export type FirstFnArg = T extends (arg: infer U) => any ? U : never; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css index e5531dc7b4b7e..0a46a14904240 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css @@ -77,7 +77,7 @@ } .inline-edits-view { - &.toolbarDropdownVisible, .editorContainer:hover { + &.toolbarDropdownVisible, .editorContainer.showHover:hover { .toolbar { display: block; } @@ -165,3 +165,22 @@ border-left: solid var(--vscode-inlineEdit-modifiedChangedTextBackground) 3px; } } + +.monaco-menu-option { + color: var(--vscode-editorActionList-foreground); + font-size: 13px; + padding: 0 10px; + line-height: 26px; + display: flex; + gap: 8px; + align-items: center; + border-radius: 4px; + cursor: pointer; + + &.active { + background: var(--vscode-editorActionList-focusBackground); + color: var(--vscode-editorActionList-focusForeground); + outline: 1px solid var(--vscode-menu-selectionBorder, transparent); + outline-offset: -1px; + } +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts index dfb11c8bebf21..4282852af5316 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts @@ -137,10 +137,12 @@ export class InlineEditsView extends Disposable { protected readonly _indicator = this._register(autorunWithStore((reader, store) => { if (this._useGutterIndicator.read(reader)) { - store.add(new InlineEditsGutterIndicator( + store.add(this._instantiationService.createInstance( + InlineEditsGutterIndicator, this._editorObs, this._uiState.map(s => s && s.originalDisplayRange), this._model, + this._sideBySide.isHovered, )); } else { store.add(new InlineEditsIndicator( From 0c51035590b20878d940ceb3cac0d54c4eb54bbc Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 20 Dec 2024 13:59:08 -0800 Subject: [PATCH 191/200] testing: fix errors peeks being oversized relative to their contents (#236763) Fixes #214011 --- src/vs/base/browser/ui/list/listView.ts | 2 + .../contrib/zoneWidget/browser/zoneWidget.ts | 14 ++-- .../contrib/debug/browser/callStackWidget.ts | 8 +++ .../testResultsView/testMessageStack.ts | 49 ------------- .../testResultsView/testResultsViewContent.ts | 37 +++++++--- .../testing/browser/testingOutputPeek.ts | 70 +++++++++++-------- 6 files changed, 85 insertions(+), 95 deletions(-) delete mode 100644 src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 36355a377ba51..e305b7de4b9a6 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -567,6 +567,8 @@ export class ListView implements IListView { if (this.supportDynamicHeights) { this._rerender(this.lastRenderTop, this.lastRenderHeight); + } else { + this._onDidChangeContentHeight.fire(this.contentHeight); // otherwise fired in _rerender() } } diff --git a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts index 51988af7d173f..a38f1639eea61 100644 --- a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts @@ -29,7 +29,6 @@ export interface IOptions { frameColor?: Color | string; arrowColor?: Color; keepEditorSelection?: boolean; - allowUnlimitedHeight?: boolean; ordinal?: number; showInHiddenAreas?: boolean; } @@ -376,6 +375,11 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { return result; } + /** Gets the maximum widget height in lines. */ + protected _getMaximumHeightInLines(): number | undefined { + return Math.max(12, (this.editor.getLayoutInfo().height / this.editor.getOption(EditorOption.lineHeight)) * 0.8); + } + private _showImpl(where: Range, heightInLines: number): void { const position = where.getStartPosition(); const layoutInfo = this.editor.getLayoutInfo(); @@ -389,8 +393,8 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { const lineHeight = this.editor.getOption(EditorOption.lineHeight); // adjust heightInLines to viewport - if (!this.options.allowUnlimitedHeight) { - const maxHeightInLines = Math.max(12, (this.editor.getLayoutInfo().height / lineHeight) * 0.8); + const maxHeightInLines = this._getMaximumHeightInLines(); + if (maxHeightInLines !== undefined) { heightInLines = Math.min(heightInLines, maxHeightInLines); } @@ -493,7 +497,9 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { // implement in subclass } - protected _relayout(newHeightInLines: number): void { + protected _relayout(_newHeightInLines: number): void { + const maxHeightInLines = this._getMaximumHeightInLines(); + const newHeightInLines = maxHeightInLines === undefined ? _newHeightInLines : Math.min(maxHeightInLines, _newHeightInLines); if (this._viewZone && this._viewZone.heightInLines !== newHeightInLines) { this.editor.changeViewZones(accessor => { if (this._viewZone) { diff --git a/src/vs/workbench/contrib/debug/browser/callStackWidget.ts b/src/vs/workbench/contrib/debug/browser/callStackWidget.ts index dc2c12dd7306b..2948bbb715b0f 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackWidget.ts @@ -119,10 +119,18 @@ export class CallStackWidget extends Disposable { private readonly currentFramesDs = this._register(new DisposableStore()); private cts?: CancellationTokenSource; + public get onDidChangeContentHeight() { + return this.list.onDidChangeContentHeight; + } + public get onDidScroll() { return this.list.onDidScroll; } + public get contentHeight() { + return this.list.contentHeight; + } + constructor( container: HTMLElement, containingEditor: ICodeEditor | undefined, diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts deleted file mode 100644 index fe0592cb567f6..0000000000000 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts +++ /dev/null @@ -1,49 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; -import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; -import { AnyStackFrame, CallStackFrame, CallStackWidget } from '../../../debug/browser/callStackWidget.js'; -import { ITestMessageStackFrame } from '../../common/testTypes.js'; - -export class TestResultStackWidget extends Disposable { - private readonly widget: CallStackWidget; - - public get onDidScroll() { - return this.widget.onDidScroll; - } - - constructor( - private readonly container: HTMLElement, - containingEditor: ICodeEditor | undefined, - @IInstantiationService instantiationService: IInstantiationService, - ) { - super(); - - this.widget = this._register(instantiationService.createInstance( - CallStackWidget, - container, - containingEditor, - )); - } - - public collapseAll() { - this.widget.collapseAll(); - } - - public update(messageFrame: AnyStackFrame, stack: ITestMessageStackFrame[]) { - this.widget.setFrames([messageFrame, ...stack.map(frame => new CallStackFrame( - frame.label, - frame.uri, - frame.position?.lineNumber, - frame.position?.column, - ))]); - } - - public layout(height?: number, width?: number) { - this.widget.layout(height ?? this.container.clientHeight, width); - } -} diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts index 421afad78f203..5f2990a413a4b 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts @@ -14,7 +14,6 @@ import { Emitter, Event, Relay } from '../../../../../base/common/event.js'; import { KeyCode } from '../../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; import { observableValue } from '../../../../../base/common/observable.js'; -import './testResultsViewContent.css'; import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; import { localize } from '../../../../../nls.js'; @@ -28,12 +27,7 @@ import { IInstantiationService, ServicesAccessor } from '../../../../../platform import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js'; import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js'; import { IUriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentity.js'; -import { CustomStackFrame } from '../../../debug/browser/callStackWidget.js'; -import * as icons from '../icons.js'; -import { TestResultStackWidget } from './testMessageStack.js'; -import { DiffContentProvider, IPeekOutputRenderer, MarkdownTestMessagePeek, PlainTextMessagePeek, TerminalMessagePeek } from './testResultsOutput.js'; -import { equalsSubject, getSubjectTestItem, InspectSubject, MessageSubject, TaskSubject, TestOutputSubject } from './testResultsSubject.js'; -import { OutputPeekTree } from './testResultsTree.js'; +import { AnyStackFrame, CallStackFrame, CallStackWidget, CustomStackFrame } from '../../../debug/browser/callStackWidget.js'; import { TestCommandId } from '../../common/constants.js'; import { IObservableValue } from '../../common/observableValue.js'; import { capabilityContextKeys, ITestProfileService } from '../../common/testProfileService.js'; @@ -41,6 +35,11 @@ import { LiveTestResult } from '../../common/testResult.js'; import { ITestFollowup, ITestService } from '../../common/testService.js'; import { ITestMessageStackFrame, TestRunProfileBitset } from '../../common/testTypes.js'; import { TestingContextKeys } from '../../common/testingContextKeys.js'; +import * as icons from '../icons.js'; +import { DiffContentProvider, IPeekOutputRenderer, MarkdownTestMessagePeek, PlainTextMessagePeek, TerminalMessagePeek } from './testResultsOutput.js'; +import { equalsSubject, getSubjectTestItem, InspectSubject, MessageSubject, TaskSubject, TestOutputSubject } from './testResultsSubject.js'; +import { OutputPeekTree } from './testResultsTree.js'; +import './testResultsViewContent.css'; const enum SubView { Diff = 0, @@ -183,7 +182,7 @@ export class TestResultsViewContent extends Disposable { private contextKeyTestMessage!: IContextKey; private contextKeyResultOutdated!: IContextKey; private stackContainer!: HTMLElement; - private callStackWidget!: TestResultStackWidget; + private callStackWidget!: CallStackWidget; private currentTopFrame?: MessageStackFrame; private isDoingLayoutUpdate?: boolean; @@ -209,6 +208,14 @@ export class TestResultsViewContent extends Disposable { }; } + public get onDidChangeContentHeight() { + return this.callStackWidget.onDidChangeContentHeight; + } + + public get contentHeight() { + return this.callStackWidget?.contentHeight || 0; + } + constructor( private readonly editor: ICodeEditor | undefined, private readonly options: { @@ -233,7 +240,7 @@ export class TestResultsViewContent extends Disposable { const messageContainer = this.messageContainer = dom.$('.test-output-peek-message-container'); this.stackContainer = dom.append(containerElement, dom.$('.test-output-call-stack-container')); - this.callStackWidget = this._register(this.instantiationService.createInstance(TestResultStackWidget, this.stackContainer, this.editor)); + this.callStackWidget = this._register(this.instantiationService.createInstance(CallStackWidget, this.stackContainer, this.editor)); this.followupWidget = this._register(this.instantiationService.createInstance(FollowupActionWidget, this.editor)); this.onCloseEmitter.input = this.followupWidget.onClose; @@ -315,13 +322,22 @@ export class TestResultsViewContent extends Disposable { this.currentSubjectStore.clear(); const callFrames = this.getCallFrames(opts.subject) || []; const topFrame = await this.prepareTopFrame(opts.subject, callFrames); - this.callStackWidget.update(topFrame, callFrames); + this.setCallStackFrames(topFrame, callFrames); this.followupWidget.show(opts.subject); this.populateFloatingClick(opts.subject); }); } + private setCallStackFrames(messageFrame: AnyStackFrame, stack: ITestMessageStackFrame[]) { + this.callStackWidget.setFrames([messageFrame, ...stack.map(frame => new CallStackFrame( + frame.label, + frame.uri, + frame.position?.lineNumber, + frame.position?.column, + ))]); + } + /** * Collapses all displayed stack frames. */ @@ -375,7 +391,6 @@ export class TestResultsViewContent extends Disposable { })); } - if (provider.onDidContentSizeChange) { this.currentSubjectStore.add(provider.onDidContentSizeChange(() => { if (this.dimension && !this.isDoingLayoutUpdate) { diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 3ea9c37de5ca0..808e68a6ca9a4 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -6,16 +6,16 @@ import * as dom from '../../../../base/browser/dom.js'; import { alert } from '../../../../base/browser/ui/aria/aria.js'; import { IAction } from '../../../../base/common/actions.js'; +import { RunOnceScheduler } from '../../../../base/common/async.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Color } from '../../../../base/common/color.js'; -import { Emitter, Event } from '../../../../base/common/event.js'; +import { Event } from '../../../../base/common/event.js'; import { stripIcons } from '../../../../base/common/iconLabels.js'; import { Iterable } from '../../../../base/common/iterator.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { Lazy } from '../../../../base/common/lazy.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { derived, disposableObservableValue, observableValue } from '../../../../base/common/observable.js'; -import { count } from '../../../../base/common/strings.js'; import { URI } from '../../../../base/common/uri.js'; import { ICodeEditor, isCodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { EditorAction2 } from '../../../../editor/browser/editorExtensions.js'; @@ -678,10 +678,8 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo class TestResultsPeek extends PeekViewWidget { - private static lastHeightInLines?: number; - - private readonly visibilityChange = this._disposables.add(new Emitter()); public readonly current = observableValue('testPeekCurrent', undefined); + private resizeOnNextContentHeightUpdate = false; private content!: TestResultsViewContent; private scopedContextKeyService!: IContextKeyService; private dimension?: dom.Dimension; @@ -701,10 +699,22 @@ class TestResultsPeek extends PeekViewWidget { super(editor, { showFrame: true, frameWidth: 1, showArrow: true, isResizeable: true, isAccessible: true, className: 'test-output-peek' }, instantiationService); this._disposables.add(themeService.onDidColorThemeChange(this.applyTheme, this)); - this._disposables.add(this.onDidClose(() => this.visibilityChange.fire(false))); peekViewService.addExclusiveWidget(editor, this); } + protected override _getMaximumHeightInLines(): number | undefined { + const defaultMaxHeight = super._getMaximumHeightInLines(); + const contentHeight = this.content?.contentHeight; + if (!contentHeight) { // undefined or 0 + return defaultMaxHeight; + } + + const lineHeight = this.editor.getOption(EditorOption.lineHeight); + // 41 is experimentally determined to be the overhead of the peek view itself + // to avoid showing scrollbars by default in its content. + return Math.min(defaultMaxHeight || Infinity, (contentHeight + 41) / lineHeight); + } + private applyTheme() { const theme = this.themeService.getColorTheme(); const current = this.current.get(); @@ -764,6 +774,27 @@ class TestResultsPeek extends PeekViewWidget { protected override _fillBody(containerElement: HTMLElement): void { this.content.fillBody(containerElement); + + // Resize on height updates for a short time to allow any heights made + // by editor contributions to come into effect before. + const contentHeightSettleTimer = this._disposables.add(new RunOnceScheduler(() => { + this.resizeOnNextContentHeightUpdate = false; + }, 500)); + + this._disposables.add(this.content.onDidChangeContentHeight(height => { + if (!this.resizeOnNextContentHeightUpdate || !height) { + return; + } + + const displayed = this._getMaximumHeightInLines(); + if (displayed) { + this._relayout(Math.min(displayed, this.getVisibleEditorLines() / 2)); + if (!contentHeightSettleTimer.isScheduled()) { + contentHeightSettleTimer.schedule(); + } + } + })); + this._disposables.add(this.content.onDidRequestReveal(sub => { TestingOutputPeekController.get(this.editor)?.show(sub instanceof MessageSubject ? sub.messageUri @@ -780,7 +811,6 @@ class TestResultsPeek extends PeekViewWidget { return this.showInPlace(subject); } - const message = subject.message; const previous = this.current; const revealLocation = subject.revealLocation?.range.getStartPosition(); if (!revealLocation && !previous) { @@ -792,13 +822,8 @@ class TestResultsPeek extends PeekViewWidget { return this.showInPlace(subject); } - // If there is a stack we want to display, ensure the default size is large-ish - const peekLines = TestResultsPeek.lastHeightInLines || Math.max( - inspectSubjectHasStack(subject) ? Math.ceil(this.getVisibleEditorLines() / 2) : 0, - hintMessagePeekHeight(message) - ); - - this.show(revealLocation, peekLines); + this.resizeOnNextContentHeightUpdate = true; + this.show(revealLocation, 10); // 10 is just a random number, we resize once content is available this.editor.revealRangeNearTopIfOutsideViewport(Range.fromPositions(revealLocation), ScrollType.Smooth); return this.showInPlace(subject); @@ -832,11 +857,6 @@ class TestResultsPeek extends PeekViewWidget { await this.content.reveal({ subject, preserveFocus: false }); } - protected override _relayout(newHeightInLines: number): void { - super._relayout(newHeightInLines); - TestResultsPeek.lastHeightInLines = newHeightInLines; - } - /** @override */ protected override _doLayoutBody(height: number, width: number) { super._doLayoutBody(height, width); @@ -919,23 +939,11 @@ export class TestResultsView extends ViewPane { } } -const hintMessagePeekHeight = (msg: ITestMessage) => { - const msgHeight = ITestMessage.isDiffable(msg) - ? Math.max(hintPeekStrHeight(msg.actual), hintPeekStrHeight(msg.expected)) - : hintPeekStrHeight(typeof msg.message === 'string' ? msg.message : msg.message.value); - - // add 8ish lines for the size of the title and decorations in the peek. - return msgHeight + 8; -}; - const firstLine = (str: string) => { const index = str.indexOf('\n'); return index === -1 ? str : str.slice(0, index); }; - -const hintPeekStrHeight = (str: string) => Math.min(count(str, '\n'), 24); - function getOuterEditorFromDiffEditor(codeEditorService: ICodeEditorService): ICodeEditor | null { const diffEditors = codeEditorService.listDiffEditors(); From 13d2a615cb98a1dc3371dcea2f03d8e65b2d83e6 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Fri, 20 Dec 2024 14:33:52 -0800 Subject: [PATCH 192/200] fix: don't register Cloud Changes action if not configured in product.json (#236764) --- .../contrib/editSessions/browser/editSessionsStorageService.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts index b056dbf64a279..1bb5eb3373700 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts @@ -455,6 +455,9 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes } private registerSignInAction() { + if (!this.serverConfiguration?.url) { + return; + } const that = this; const id = 'workbench.editSessions.actions.signIn'; const when = ContextKeyExpr.and(ContextKeyExpr.equals(EDIT_SESSIONS_PENDING_KEY, false), ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, false)); From 68252d3d7b25bc50c67abad5f32c885012deb985 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Fri, 20 Dec 2024 20:22:20 -0800 Subject: [PATCH 193/200] fix: don't leak listener in share contribution (#236767) --- .../contrib/share/browser/share.contribution.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/share/browser/share.contribution.ts b/src/vs/workbench/contrib/share/browser/share.contribution.ts index cd1cfc176e01b..3566792aa9d5c 100644 --- a/src/vs/workbench/contrib/share/browser/share.contribution.ts +++ b/src/vs/workbench/contrib/share/browser/share.contribution.ts @@ -32,7 +32,7 @@ import { IProgressService, ProgressLocation } from '../../../../platform/progres import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../../../platform/configuration/common/configurationRegistry.js'; import { workbenchConfigurationNodeBase } from '../../../common/configuration.js'; -import { DisposableStore } from '../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; const targetMenus = [ MenuId.EditorContextShare, @@ -44,7 +44,7 @@ const targetMenus = [ MenuId.ExplorerContextShare ]; -class ShareWorkbenchContribution { +class ShareWorkbenchContribution extends Disposable { private static SHARE_ENABLED_SETTING = 'workbench.experimental.share.enabled'; private _disposables: DisposableStore | undefined; @@ -53,10 +53,12 @@ class ShareWorkbenchContribution { @IShareService private readonly shareService: IShareService, @IConfigurationService private readonly configurationService: IConfigurationService ) { + super(); + if (this.configurationService.getValue(ShareWorkbenchContribution.SHARE_ENABLED_SETTING)) { this.registerActions(); } - this.configurationService.onDidChangeConfiguration(e => { + this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(ShareWorkbenchContribution.SHARE_ENABLED_SETTING)) { const settingValue = this.configurationService.getValue(ShareWorkbenchContribution.SHARE_ENABLED_SETTING); if (settingValue === true && this._disposables === undefined) { @@ -66,7 +68,12 @@ class ShareWorkbenchContribution { this._disposables = undefined; } } - }); + })); + } + + override dispose(): void { + super.dispose(); + this._disposables?.dispose(); } private registerActions() { From d4de5ceba46823bafe63733df924e63990afef72 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Fri, 20 Dec 2024 20:23:36 -0800 Subject: [PATCH 194/200] test: adopt `ensureNoDisposablesAreLeakedInTestSuite` (#236766) * test: adopt `ensureNoDisposablesAreLeakedInTestSuite` * test: adopt `ensureNoDisposablesAreLeakedInTestSuite` --- eslint.config.js | 1 - .../editSessions/test/browser/editSessions.test.ts | 9 +++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/eslint.config.js b/eslint.config.js index aa9fd4930c51b..db83d096edde0 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -229,7 +229,6 @@ export default tseslint.config( 'src/vs/workbench/api/test/node/extHostTunnelService.test.ts', 'src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts', 'src/vs/workbench/contrib/chat/test/common/chatWordCounter.test.ts', - 'src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts', 'src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts', 'src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts', 'src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts', diff --git a/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts index 38aeb6c161b9e..359c7be57d51d 100644 --- a/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts +++ b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts @@ -50,11 +50,13 @@ import { TestStorageService } from '../../../../test/common/workbenchTestService import { IUriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentity.js'; import { UriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentityService.js'; import { IWorkspaceIdentityService, WorkspaceIdentityService } from '../../../../services/workspaces/common/workspaceIdentityService.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; const folderName = 'test-folder'; const folderUri = URI.file(`/${folderName}`); suite('Edit session sync', () => { + let instantiationService: TestInstantiationService; let editSessionsContribution: EditSessionsContribution; let fileService: FileService; @@ -63,6 +65,7 @@ suite('Edit session sync', () => { const disposables = new DisposableStore(); suiteSetup(() => { + sandbox = sinon.createSandbox(); instantiationService = new TestInstantiationService(); @@ -172,6 +175,10 @@ suite('Edit session sync', () => { disposables.clear(); }); + suiteTeardown(() => { + disposables.dispose(); + }); + test('Can apply edit session', async function () { const fileUri = joinPath(folderUri, 'dir1', 'README.md'); const fileContents = '# readme'; @@ -218,4 +225,6 @@ suite('Edit session sync', () => { // Verify that we did not attempt to write the edit session assert.equal(writeStub.called, false); }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); From d6a59b79698b81cc1f0a74113189b7106453bb78 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Sat, 21 Dec 2024 12:59:52 -0800 Subject: [PATCH 195/200] debug: fix mismatched indentation for folders in loaded scripts (#236750) * debug: fix mismatched indentation for folders in loaded scripts Fixes #228241 * fix test --- .../contrib/debug/browser/baseDebugView.ts | 2 +- .../debug/browser/loadedScriptsView.ts | 78 ++++++++++--------- .../debug/test/browser/baseDebugView.test.ts | 2 +- 3 files changed, 44 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 9576b7e5bb78f..d5ba5f0c85615 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -51,7 +51,7 @@ export interface IVariableTemplateData { export function renderViewTree(container: HTMLElement): HTMLElement { const treeContainer = $('.'); - treeContainer.classList.add('debug-view-content'); + treeContainer.classList.add('debug-view-content', 'file-icon-themable-tree'); container.appendChild(treeContainer); return treeContainer; } diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 3c6e37f7bc0f2..4876cc3a79779 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -3,47 +3,46 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -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 { 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 { registerAction2, MenuId } from '../../../../platform/actions/common/actions.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 { IViewDescriptorService } from '../../../common/views.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 { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { IFileIconTheme, 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 { TreeFindMode } from '../../../../base/browser/ui/tree/abstractTree.js'; -import { IHoverService } from '../../../../platform/hover/browser/hover.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'; const NEW_STYLE_COMPRESS = true; @@ -439,7 +438,7 @@ export class LoadedScriptsView extends ViewPane { @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, - @IHoverService hoverService: IHoverService + @IHoverService hoverService: IHoverService, ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService); @@ -449,8 +448,7 @@ export class LoadedScriptsView extends ViewPane { super.renderBody(container); this.element.classList.add('debug-pane'); - container.classList.add('debug-loaded-scripts'); - container.classList.add('show-file-icons'); + container.classList.add('debug-loaded-scripts', 'show-file-icons'); this.treeContainer = renderViewTree(container); @@ -461,6 +459,14 @@ export class LoadedScriptsView extends ViewPane { this.treeLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this._register(this.treeLabels); + 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._register(this.themeService.onDidFileIconThemeChange(onFileIconThemeChange)); + onFileIconThemeChange(this.themeService.getFileIconTheme()); + this.tree = this.instantiationService.createInstance(WorkbenchCompressibleObjectTree, 'LoadedScriptsView', this.treeContainer, diff --git a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts index fcefad3c51672..9122412e421ee 100644 --- a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts @@ -106,7 +106,7 @@ suite('Debug - Base Debug View', () => { const container = $('.container'); const treeContainer = renderViewTree(container); - assert.strictEqual(treeContainer.className, 'debug-view-content'); + assert.strictEqual(treeContainer.className, 'debug-view-content file-icon-themable-tree'); assert.strictEqual(container.childElementCount, 1); assert.strictEqual(container.firstChild, treeContainer); assert.strictEqual(dom.isHTMLDivElement(treeContainer), true); From d953d84d9018aef819147320ed58e5b517d2b0a2 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Sat, 21 Dec 2024 13:01:00 -0800 Subject: [PATCH 196/200] debug: allow filter/search on debug values (#236768) * debug: allow filter/search on debug values I think this is something that never worked, or at least not for a long while. Implementing match highlighting in values in the era of linkification and ANSI support was a little more complex, but this works well now. ![](https://memes.peet.io/img/24-12-b1f699dc-f20c-4c93-a5ce-f768473fff62.png) Fixes #230945 * fix test --- .../contrib/debug/browser/baseDebugView.ts | 30 ++++++- .../debug/browser/debugANSIHandling.ts | 29 ++++-- .../debug/browser/debugExpressionRenderer.ts | 13 +-- .../contrib/debug/browser/linkDetector.ts | 89 +++++++++++++++---- .../debug/browser/media/debugViewlet.css | 6 ++ .../contrib/debug/browser/variablesView.ts | 4 +- .../debug/browser/watchExpressionsView.ts | 4 +- .../test/browser/debugANSIHandling.test.ts | 8 +- .../debug/test/browser/linkDetector.test.ts | 64 +++++++++++++ 9 files changed, 208 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index d5ba5f0c85615..efb22ae81c13d 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -9,19 +9,21 @@ import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { HighlightedLabel, IHighlight } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js'; import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { IInputValidationOptions, InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js'; +import { IKeyboardNavigationLabelProvider } from '../../../../base/browser/ui/list/list.js'; import { IAsyncDataSource, ITreeNode, ITreeRenderer } from '../../../../base/browser/ui/tree/tree.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { FuzzyScore, createMatches } from '../../../../base/common/filters.js'; import { createSingleCallFunction } from '../../../../base/common/functional.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; import { DisposableStore, IDisposable, dispose, toDisposable } from '../../../../base/common/lifecycle.js'; +import { removeAnsiEscapeCodes } from '../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { localize } from '../../../../nls.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IContextViewService } from '../../../../platform/contextview/browser/contextView.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { defaultInputBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js'; -import { IDebugService, IExpression } from '../common/debug.js'; +import { IDebugService, IExpression, IScope } from '../common/debug.js'; import { Variable } from '../common/debugModel.js'; import { IDebugVisualizerService } from '../common/debugVisualizers.js'; import { LinkDetector } from './linkDetector.js'; @@ -78,6 +80,32 @@ export interface IExpressionTemplateData { currentElement: IExpression | undefined; } +/** Splits highlights based on matching of the {@link expressionAndScopeLabelProvider} */ +export const splitExpressionOrScopeHighlights = (e: IExpression | IScope, highlights: IHighlight[]) => { + const nameEndsAt = e.name.length; + const labelBeginsAt = e.name.length + 2; + const name: IHighlight[] = []; + const value: IHighlight[] = []; + for (const hl of highlights) { + if (hl.start < nameEndsAt) { + name.push({ start: hl.start, end: Math.min(hl.end, nameEndsAt) }); + } + if (hl.end > labelBeginsAt) { + value.push({ start: Math.max(hl.start - labelBeginsAt, 0), end: hl.end - labelBeginsAt }); + } + } + + return { name, value }; +}; + +/** Keyboard label provider for expression and scope tree elements. */ +export const expressionAndScopeLabelProvider: IKeyboardNavigationLabelProvider = { + getKeyboardNavigationLabel(e) { + const stripAnsi = e.getSession()?.rememberedCapabilities?.supportsANSIStyling; + return `${e.name}: ${stripAnsi ? removeAnsiEscapeCodes(e.value) : e.value}`; + }, +}; + export abstract class AbstractExpressionDataSource implements IAsyncDataSource { constructor( @IDebugService protected debugService: IDebugService, diff --git a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts index 16923a73f61d5..4db2d7eb8c77d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts +++ b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IHighlight } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js'; import { Color, RGBA } from '../../../../base/common/color.js'; import { isDefined } from '../../../../base/common/types.js'; import { editorHoverBackground, listActiveSelectionBackground, listFocusBackground, listInactiveFocusBackground, listInactiveSelectionBackground } from '../../../../platform/theme/common/colorRegistry.js'; @@ -16,7 +17,7 @@ import { ILinkDetector } from './linkDetector.js'; * @param text The content to stylize. * @returns An {@link HTMLSpanElement} that contains the potentially stylized text. */ -export function handleANSIOutput(text: string, linkDetector: ILinkDetector, workspaceFolder: IWorkspaceFolder | undefined): HTMLSpanElement { +export function handleANSIOutput(text: string, linkDetector: ILinkDetector, workspaceFolder: IWorkspaceFolder | undefined, highlights: IHighlight[] | undefined): HTMLSpanElement { const root: HTMLSpanElement = document.createElement('span'); const textLength: number = text.length; @@ -27,6 +28,7 @@ export function handleANSIOutput(text: string, linkDetector: ILinkDetector, work let customUnderlineColor: RGBA | string | undefined; let colorsInverted: boolean = false; let currentPos: number = 0; + let unprintedChars = 0; let buffer: string = ''; while (currentPos < textLength) { @@ -58,8 +60,10 @@ export function handleANSIOutput(text: string, linkDetector: ILinkDetector, work if (sequenceFound) { + unprintedChars += 2 + ansiSequence.length; + // Flush buffer with previous styles. - appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor, customUnderlineColor); + appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor, customUnderlineColor, highlights, currentPos - buffer.length - unprintedChars); buffer = ''; @@ -105,7 +109,7 @@ export function handleANSIOutput(text: string, linkDetector: ILinkDetector, work // Flush remaining text buffer if not empty. if (buffer) { - appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor, customUnderlineColor); + appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor, customUnderlineColor, highlights, currentPos - buffer.length); } return root; @@ -395,6 +399,8 @@ export function handleANSIOutput(text: string, linkDetector: ILinkDetector, work * @param customTextColor If provided, will apply custom color with inline style. * @param customBackgroundColor If provided, will apply custom backgroundColor with inline style. * @param customUnderlineColor If provided, will apply custom textDecorationColor with inline style. + * @param highlights The ranges to highlight. + * @param offset The starting index of the stringContent in the original text. */ export function appendStylizedStringToContainer( root: HTMLElement, @@ -402,15 +408,24 @@ export function appendStylizedStringToContainer( cssClasses: string[], linkDetector: ILinkDetector, workspaceFolder: IWorkspaceFolder | undefined, - customTextColor?: RGBA | string, - customBackgroundColor?: RGBA | string, - customUnderlineColor?: RGBA | string, + customTextColor: RGBA | string | undefined, + customBackgroundColor: RGBA | string | undefined, + customUnderlineColor: RGBA | string | undefined, + highlights: IHighlight[] | undefined, + offset: number, ): void { if (!root || !stringContent) { return; } - const container = linkDetector.linkify(stringContent, true, workspaceFolder); + const container = linkDetector.linkify( + stringContent, + true, + workspaceFolder, + undefined, + undefined, + highlights?.map(h => ({ start: h.start - offset, end: h.end - offset, extraClasses: h.extraClasses })), + ); container.className = cssClasses.join(' '); if (customTextColor) { diff --git a/src/vs/workbench/contrib/debug/browser/debugExpressionRenderer.ts b/src/vs/workbench/contrib/debug/browser/debugExpressionRenderer.ts index 767950dc826c8..0835365d4081e 100644 --- a/src/vs/workbench/contrib/debug/browser/debugExpressionRenderer.ts +++ b/src/vs/workbench/contrib/debug/browser/debugExpressionRenderer.ts @@ -16,7 +16,7 @@ import { observableConfigValue } from '../../../../platform/observable/common/pl import { IDebugSession, IExpressionValue } from '../common/debug.js'; import { Expression, ExpressionContainer, Variable } from '../common/debugModel.js'; import { ReplEvaluationResult } from '../common/replModel.js'; -import { IVariableTemplateData } from './baseDebugView.js'; +import { IVariableTemplateData, splitExpressionOrScopeHighlights } from './baseDebugView.js'; import { handleANSIOutput } from './debugANSIHandling.js'; import { COPY_EVALUATE_PATH_ID, COPY_VALUE_ID } from './debugCommands.js'; import { DebugLinkHoverBehavior, DebugLinkHoverBehaviorTypeData, ILinkDetector, LinkDetector } from './linkDetector.js'; @@ -32,6 +32,7 @@ export interface IRenderValueOptions { /** If not false, a rich hover will be shown on the element. */ hover?: false | IValueHoverOptions; colorize?: boolean; + highlights?: IHighlight[]; /** * Indicates areas where VS Code implicitly always supported ANSI escape @@ -90,6 +91,7 @@ export class DebugExpressionRenderer { renderVariable(data: IVariableTemplateData, variable: Variable, options: IRenderVariableOptions = {}): IDisposable { const displayType = this.displayType.get(); + const highlights = splitExpressionOrScopeHighlights(variable, options.highlights || []); if (variable.available) { data.type.textContent = ''; @@ -103,7 +105,7 @@ export class DebugExpressionRenderer { } } - data.label.set(text, options.highlights, variable.type && !displayType ? variable.type : variable.name); + data.label.set(text, highlights.name, variable.type && !displayType ? variable.type : variable.name); data.name.classList.toggle('virtual', variable.presentationHint?.kind === 'virtual'); data.name.classList.toggle('internal', variable.presentationHint?.visibility === 'internal'); } else if (variable.value && typeof variable.name === 'string' && variable.name) { @@ -122,6 +124,7 @@ export class DebugExpressionRenderer { showChanged: options.showChanged, maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET, hover: { commands }, + highlights: highlights.value, colorize: true, session: variable.getSession(), }); @@ -184,9 +187,9 @@ export class DebugExpressionRenderer { } if (supportsANSI) { - container.appendChild(handleANSIOutput(value, linkDetector, session ? session.root : undefined)); + container.appendChild(handleANSIOutput(value, linkDetector, session ? session.root : undefined, options.highlights)); } else { - container.appendChild(linkDetector.linkify(value, false, session?.root, true, hoverBehavior)); + container.appendChild(linkDetector.linkify(value, false, session?.root, true, hoverBehavior, options.highlights)); } if (options.hover !== false) { @@ -199,7 +202,7 @@ export class DebugExpressionRenderer { if (supportsANSI) { // note: intentionally using `this.linkDetector` so we don't blindly linkify the // entire contents and instead only link file paths that it contains. - hoverContentsPre.appendChild(handleANSIOutput(value, this.linkDetector, session ? session.root : undefined)); + hoverContentsPre.appendChild(handleANSIOutput(value, this.linkDetector, session ? session.root : undefined, options.highlights)); } else { hoverContentsPre.textContent = value; } diff --git a/src/vs/workbench/contrib/debug/browser/linkDetector.ts b/src/vs/workbench/contrib/debug/browser/linkDetector.ts index 98a89453a5ad3..8d08e6f1be93b 100644 --- a/src/vs/workbench/contrib/debug/browser/linkDetector.ts +++ b/src/vs/workbench/contrib/debug/browser/linkDetector.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getWindow } from '../../../../base/browser/dom.js'; +import { getWindow, isHTMLElement, reset } from '../../../../base/browser/dom.js'; import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; @@ -23,6 +23,8 @@ import { IDebugSession } from '../common/debug.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; import { IPathService } from '../../../services/path/common/pathService.js'; +import { IHighlight } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js'; +import { Iterable } from '../../../../base/common/iterator.js'; const CONTROL_CODES = '\\u0000-\\u0020\\u007f-\\u009f'; const WEB_LINK_REGEX = new RegExp('(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|data:|www\\.)[^\\s' + CONTROL_CODES + '"]{2,}[^\\s' + CONTROL_CODES + '"\')}\\],:;.!?]', 'ug'); @@ -42,6 +44,7 @@ type LinkPart = { kind: LinkKind; value: string; captures: string[]; + index: number; }; export const enum DebugLinkHoverBehavior { @@ -61,7 +64,7 @@ export type DebugLinkHoverBehaviorTypeData = { type: DebugLinkHoverBehavior.None | { type: DebugLinkHoverBehavior.Rich; store: DisposableStore }; export interface ILinkDetector { - linkify(text: string, splitLines?: boolean, workspaceFolder?: IWorkspaceFolder, includeFulltext?: boolean, hoverBehavior?: DebugLinkHoverBehaviorTypeData): HTMLElement; + linkify(text: string, splitLines?: boolean, workspaceFolder?: IWorkspaceFolder, includeFulltext?: boolean, hoverBehavior?: DebugLinkHoverBehaviorTypeData, highlights?: IHighlight[]): HTMLElement; linkifyLocation(text: string, locationReference: number, session: IDebugSession, hoverBehavior?: DebugLinkHoverBehaviorTypeData): HTMLElement; } @@ -88,11 +91,11 @@ export class LinkDetector implements ILinkDetector { * If a `hoverBehavior` is passed, hovers may be added using the workbench hover service. * This should be preferred for new code where hovers are desirable. */ - linkify(text: string, splitLines?: boolean, workspaceFolder?: IWorkspaceFolder, includeFulltext?: boolean, hoverBehavior?: DebugLinkHoverBehaviorTypeData): HTMLElement { - return this._linkify(text, splitLines, workspaceFolder, includeFulltext, hoverBehavior); + linkify(text: string, splitLines?: boolean, workspaceFolder?: IWorkspaceFolder, includeFulltext?: boolean, hoverBehavior?: DebugLinkHoverBehaviorTypeData, highlights?: IHighlight[]): HTMLElement { + return this._linkify(text, splitLines, workspaceFolder, includeFulltext, hoverBehavior, highlights); } - private _linkify(text: string, splitLines?: boolean, workspaceFolder?: IWorkspaceFolder, includeFulltext?: boolean, hoverBehavior?: DebugLinkHoverBehaviorTypeData, defaultRef?: { locationReference: number; session: IDebugSession }): HTMLElement { + private _linkify(text: string, splitLines?: boolean, workspaceFolder?: IWorkspaceFolder, includeFulltext?: boolean, hoverBehavior?: DebugLinkHoverBehaviorTypeData, highlights?: IHighlight[], defaultRef?: { locationReference: number; session: IDebugSession }): HTMLElement { if (splitLines) { const lines = text.split('\n'); for (let i = 0; i < lines.length - 1; i++) { @@ -102,7 +105,7 @@ export class LinkDetector implements ILinkDetector { // Remove the last element ('') that split added. lines.pop(); } - const elements = lines.map(line => this._linkify(line, false, workspaceFolder, includeFulltext, hoverBehavior, defaultRef)); + const elements = lines.map(line => this._linkify(line, false, workspaceFolder, includeFulltext, hoverBehavior, highlights, defaultRef)); if (elements.length === 1) { // Do not wrap single line with extra span. return elements[0]; @@ -115,21 +118,26 @@ export class LinkDetector implements ILinkDetector { const container = document.createElement('span'); for (const part of this.detectLinks(text)) { try { + let node: Node; switch (part.kind) { case 'text': - container.appendChild(defaultRef ? this.linkifyLocation(part.value, defaultRef.locationReference, defaultRef.session, hoverBehavior) : document.createTextNode(part.value)); + node = defaultRef ? this.linkifyLocation(part.value, defaultRef.locationReference, defaultRef.session, hoverBehavior) : document.createTextNode(part.value); break; case 'web': - container.appendChild(this.createWebLink(includeFulltext ? text : undefined, part.value, hoverBehavior)); + node = this.createWebLink(includeFulltext ? text : undefined, part.value, hoverBehavior); break; case 'path': { const path = part.captures[0]; const lineNumber = part.captures[1] ? Number(part.captures[1]) : 0; const columnNumber = part.captures[2] ? Number(part.captures[2]) : 0; - container.appendChild(this.createPathLink(includeFulltext ? text : undefined, part.value, path, lineNumber, columnNumber, workspaceFolder, hoverBehavior)); + node = this.createPathLink(includeFulltext ? text : undefined, part.value, path, lineNumber, columnNumber, workspaceFolder, hoverBehavior); break; } + default: + node = document.createTextNode(part.value); } + + container.append(...this.applyHighlights(node, part.index, part.value.length, highlights)); } catch (e) { container.appendChild(document.createTextNode(part.value)); } @@ -137,6 +145,50 @@ export class LinkDetector implements ILinkDetector { return container; } + private applyHighlights(node: Node, startIndex: number, length: number, highlights: IHighlight[] | undefined): Iterable { + const children: (Node | string)[] = []; + let currentIndex = startIndex; + const endIndex = startIndex + length; + + for (const highlight of highlights || []) { + if (highlight.end <= currentIndex || highlight.start >= endIndex) { + continue; + } + + if (highlight.start > currentIndex) { + children.push(node.textContent!.substring(currentIndex - startIndex, highlight.start - startIndex)); + currentIndex = highlight.start; + } + + const highlightEnd = Math.min(highlight.end, endIndex); + const highlightedText = node.textContent!.substring(currentIndex - startIndex, highlightEnd - startIndex); + const highlightSpan = document.createElement('span'); + highlightSpan.classList.add('highlight'); + if (highlight.extraClasses) { + highlightSpan.classList.add(...highlight.extraClasses); + } + highlightSpan.textContent = highlightedText; + children.push(highlightSpan); + currentIndex = highlightEnd; + } + + if (currentIndex === startIndex) { + return Iterable.single(node); // no changes made + } + + if (currentIndex < endIndex) { + children.push(node.textContent!.substring(currentIndex - startIndex)); + } + + // reuse the element if it's a link + if (isHTMLElement(node)) { + reset(node, ...children); + return Iterable.single(node); + } + + return children; + } + /** * Linkifies a location reference. */ @@ -161,8 +213,8 @@ export class LinkDetector implements ILinkDetector { */ makeReferencedLinkDetector(locationReference: number, session: IDebugSession): ILinkDetector { return { - linkify: (text, splitLines, workspaceFolder, includeFulltext, hoverBehavior) => - this._linkify(text, splitLines, workspaceFolder, includeFulltext, hoverBehavior, { locationReference, session }), + linkify: (text, splitLines, workspaceFolder, includeFulltext, hoverBehavior, highlights) => + this._linkify(text, splitLines, workspaceFolder, includeFulltext, hoverBehavior, highlights, { locationReference, session }), linkifyLocation: this.linkifyLocation.bind(this), }; } @@ -295,16 +347,16 @@ export class LinkDetector implements ILinkDetector { private detectLinks(text: string): LinkPart[] { if (text.length > MAX_LENGTH) { - return [{ kind: 'text', value: text, captures: [] }]; + return [{ kind: 'text', value: text, captures: [], index: 0 }]; } const regexes: RegExp[] = [WEB_LINK_REGEX, PATH_LINK_REGEX]; const kinds: LinkKind[] = ['web', 'path']; const result: LinkPart[] = []; - const splitOne = (text: string, regexIndex: number) => { + const splitOne = (text: string, regexIndex: number, baseIndex: number) => { if (regexIndex >= regexes.length) { - result.push({ value: text, kind: 'text', captures: [] }); + result.push({ value: text, kind: 'text', captures: [], index: baseIndex }); return; } const regex = regexes[regexIndex]; @@ -314,23 +366,24 @@ export class LinkDetector implements ILinkDetector { while ((match = regex.exec(text)) !== null) { const stringBeforeMatch = text.substring(currentIndex, match.index); if (stringBeforeMatch) { - splitOne(stringBeforeMatch, regexIndex + 1); + splitOne(stringBeforeMatch, regexIndex + 1, baseIndex + currentIndex); } const value = match[0]; result.push({ value: value, kind: kinds[regexIndex], - captures: match.slice(1) + captures: match.slice(1), + index: baseIndex + match.index }); currentIndex = match.index + value.length; } const stringAfterMatches = text.substring(currentIndex); if (stringAfterMatches) { - splitOne(stringAfterMatches, regexIndex + 1); + splitOne(stringAfterMatches, regexIndex + 1, baseIndex + currentIndex); } }; - splitOne(text, 0); + splitOne(text, 0, 0); return result; } } diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 90dfb51bc6007..f795c6c22ebd7 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -274,6 +274,12 @@ font-family: var(--monaco-monospace-font); font-weight: normal; } +.debug-view-content .monaco-tl-contents .highlight { + color: unset !important; + background-color: var(--vscode-list-filterMatchBackground); + outline: 1px dotted var(--vscode-list-filterMatchBorder); + outline-offset: -1px; +} /* Breakpoints */ diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 16d53c4f87ee0..74c22d9ee976e 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -46,7 +46,7 @@ import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS 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 { AbstractExpressionDataSource, AbstractExpressionsRenderer, expressionAndScopeLabelProvider, 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'; @@ -138,7 +138,7 @@ export class VariablesView extends ViewPane implements IDebugViewWithVariables { this.instantiationService.createInstance(VariablesDataSource), { accessibilityProvider: new VariablesAccessibilityProvider(), identityProvider: { getId: (element: IExpression | IScope) => element.getId() }, - keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression | IScope) => e.name }, + keyboardNavigationLabelProvider: expressionAndScopeLabelProvider, overrideStyles: this.getLocationBasedColors().listOverrideStyles }); diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index b325c138b0a35..a56d4aae46237 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -31,7 +31,7 @@ import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.j 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, IDebugViewWithVariables, 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 { AbstractExpressionDataSource, AbstractExpressionsRenderer, expressionAndScopeLabelProvider, IExpressionTemplateData, IInputBoxOptions, renderViewTree } from './baseDebugView.js'; import { DebugExpressionRenderer } from './debugExpressionRenderer.js'; import { watchExpressionsAdd, watchExpressionsRemoveAll } from './debugIcons.js'; import { VariablesRenderer, VisualizedVariableRenderer } from './variablesView.js'; @@ -109,7 +109,7 @@ export class WatchExpressionsView extends ViewPane implements IDebugViewWithVari return undefined; } - return e.name; + return expressionAndScopeLabelProvider.getKeyboardNavigationLabel(e); } }, dnd: new WatchExpressionsDragAndDrop(this.debugService), diff --git a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts index eb31a388b2eda..17d23dc630324 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts @@ -51,8 +51,8 @@ suite('Debug - ANSI Handling', () => { assert.strictEqual(0, root.children.length); - appendStylizedStringToContainer(root, 'content1', ['class1', 'class2'], linkDetector, session.root); - appendStylizedStringToContainer(root, 'content2', ['class2', 'class3'], linkDetector, session.root); + appendStylizedStringToContainer(root, 'content1', ['class1', 'class2'], linkDetector, session.root, undefined, undefined, undefined, undefined, 0); + appendStylizedStringToContainer(root, 'content2', ['class2', 'class3'], linkDetector, session.root, undefined, undefined, undefined, undefined, 0); assert.strictEqual(2, root.children.length); @@ -82,7 +82,7 @@ suite('Debug - ANSI Handling', () => { * @returns An {@link HTMLSpanElement} that contains the stylized text. */ function getSequenceOutput(sequence: string): HTMLSpanElement { - const root: HTMLSpanElement = handleANSIOutput(sequence, linkDetector, session.root); + const root: HTMLSpanElement = handleANSIOutput(sequence, linkDetector, session.root, []); assert.strictEqual(1, root.children.length); const child: Node = root.lastChild!; if (isHTMLSpanElement(child)) { @@ -395,7 +395,7 @@ suite('Debug - ANSI Handling', () => { if (elementsExpected === undefined) { elementsExpected = assertions.length; } - const root: HTMLSpanElement = handleANSIOutput(sequence, linkDetector, session.root); + const root: HTMLSpanElement = handleANSIOutput(sequence, linkDetector, session.root, []); assert.strictEqual(elementsExpected, root.children.length); for (let i = 0; i < elementsExpected; i++) { const child: Node = root.children[i]; diff --git a/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts b/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts index c078c4a371e56..9e2d0c8c767b9 100644 --- a/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts @@ -13,6 +13,7 @@ import { ITunnelService } from '../../../../../platform/tunnel/common/tunnel.js' import { WorkspaceFolder } from '../../../../../platform/workspace/common/workspace.js'; import { LinkDetector } from '../../browser/linkDetector.js'; import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; +import { IHighlight } from '../../../../../base/browser/ui/highlightedlabel/highlightedLabel.js'; suite('Debug - Link Detector', () => { @@ -168,4 +169,67 @@ suite('Debug - Link Detector', () => { assertElementIsLink(output.children[1].children[0]); assert.strictEqual(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.children[1].children[0].textContent); }); + + test('highlightNoLinks', () => { + const input = 'I am a string'; + const highlights: IHighlight[] = [{ start: 2, end: 5 }]; + const expectedOutput = 'I am a string'; + const output = linkDetector.linkify(input, false, undefined, false, undefined, highlights); + + assert.strictEqual(1, output.children.length); + assert.strictEqual('SPAN', output.tagName); + assert.strictEqual(expectedOutput, output.outerHTML); + }); + + test('highlightWithLink', () => { + const input = isWindows ? 'C:\\foo\\bar.js:12:34' : '/Users/foo/bar.js:12:34'; + const highlights: IHighlight[] = [{ start: 0, end: 5 }]; + const expectedOutput = isWindows ? 'C:\\foo\\bar.js:12:34' : '/Users/foo/bar.js:12:34'; + const output = linkDetector.linkify(input, false, undefined, false, undefined, highlights); + + assert.strictEqual(1, output.children.length); + assert.strictEqual('SPAN', output.tagName); + assert.strictEqual('A', output.firstElementChild!.tagName); + assert.strictEqual(expectedOutput, output.outerHTML); + assertElementIsLink(output.firstElementChild!); + }); + + test('highlightOverlappingLinkStart', () => { + const input = isWindows ? 'C:\\foo\\bar.js:12:34' : '/Users/foo/bar.js:12:34'; + const highlights: IHighlight[] = [{ start: 0, end: 10 }]; + const expectedOutput = isWindows ? 'C:\\foo\\bar.js:12:34' : '/Users/foo/bar.js:12:34'; + const output = linkDetector.linkify(input, false, undefined, false, undefined, highlights); + + assert.strictEqual(1, output.children.length); + assert.strictEqual('SPAN', output.tagName); + assert.strictEqual('A', output.firstElementChild!.tagName); + assert.strictEqual(expectedOutput, output.outerHTML); + assertElementIsLink(output.firstElementChild!); + }); + + test('highlightOverlappingLinkEnd', () => { + const input = isWindows ? 'C:\\foo\\bar.js:12:34' : '/Users/foo/bar.js:12:34'; + const highlights: IHighlight[] = [{ start: 10, end: 20 }]; + const expectedOutput = isWindows ? 'C:\\foo\\bar.js:12:34' : '/Users/foo/bar.js:12:34'; + const output = linkDetector.linkify(input, false, undefined, false, undefined, highlights); + + assert.strictEqual(1, output.children.length); + assert.strictEqual('SPAN', output.tagName); + assert.strictEqual('A', output.firstElementChild!.tagName); + assert.strictEqual(expectedOutput, output.outerHTML); + assertElementIsLink(output.firstElementChild!); + }); + + test('highlightOverlappingLinkStartAndEnd', () => { + const input = isWindows ? 'C:\\foo\\bar.js:12:34' : '/Users/foo/bar.js:12:34'; + const highlights: IHighlight[] = [{ start: 5, end: 15 }]; + const expectedOutput = isWindows ? 'C:\\foo\\bar.js:12:34' : '/Users/foo/bar.js:12:34'; + const output = linkDetector.linkify(input, false, undefined, false, undefined, highlights); + + assert.strictEqual(1, output.children.length); + assert.strictEqual('SPAN', output.tagName); + assert.strictEqual('A', output.firstElementChild!.tagName); + assert.strictEqual(expectedOutput, output.outerHTML); + assertElementIsLink(output.firstElementChild!); + }); }); From 151ef3514e76629f4e7bf3951439b1e0dae0a6e5 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Sun, 22 Dec 2024 14:09:59 +0100 Subject: [PATCH 197/200] SCM - disable actions for resource groups that do not have any resources (#236813) --- extensions/git/package.json | 14 ++++++------- src/vs/workbench/contrib/scm/browser/menus.ts | 20 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 53aa0fad747e3..4556255d3dac0 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -163,14 +163,14 @@ "title": "%command.stageAll%", "category": "Git", "icon": "$(add)", - "enablement": "!operationInProgress" + "enablement": "!operationInProgress && scmResourceGroupResourceCount > 0" }, { "command": "git.stageAllTracked", "title": "%command.stageAllTracked%", "category": "Git", "icon": "$(add)", - "enablement": "!operationInProgress" + "enablement": "!operationInProgress && scmResourceGroupResourceCount > 0" }, { "command": "git.stageAllUntracked", @@ -243,7 +243,7 @@ "title": "%command.unstageAll%", "category": "Git", "icon": "$(remove)", - "enablement": "!operationInProgress" + "enablement": "!operationInProgress && scmResourceGroupResourceCount > 0" }, { "command": "git.unstageSelectedRanges", @@ -270,14 +270,14 @@ "title": "%command.cleanAll%", "category": "Git", "icon": "$(discard)", - "enablement": "!operationInProgress" + "enablement": "!operationInProgress && scmResourceGroupResourceCount > 0" }, { "command": "git.cleanAllTracked", "title": "%command.cleanAllTracked%", "category": "Git", "icon": "$(discard)", - "enablement": "!operationInProgress" + "enablement": "!operationInProgress && scmResourceGroupResourceCount > 0" }, { "command": "git.cleanAllUntracked", @@ -889,14 +889,14 @@ "title": "%command.viewChanges%", "icon": "$(diff-multiple)", "category": "Git", - "enablement": "!operationInProgress" + "enablement": "!operationInProgress && scmResourceGroupResourceCount > 0" }, { "command": "git.viewStagedChanges", "title": "%command.viewStagedChanges%", "icon": "$(diff-multiple)", "category": "Git", - "enablement": "!operationInProgress" + "enablement": "!operationInProgress && scmResourceGroupResourceCount > 0" }, { "command": "git.viewUntrackedChanges", diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index f14d8ea79f67c..352f63b6f3ec9 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -6,7 +6,7 @@ import { IAction } from '../../../../base/common/actions.js'; import { equals } from '../../../../base/common/arrays.js'; import { Emitter } from '../../../../base/common/event.js'; -import { DisposableStore, IDisposable, dispose } from '../../../../base/common/lifecycle.js'; +import { DisposableStore, IDisposable, MutableDisposable, dispose } from '../../../../base/common/lifecycle.js'; import './media/scm.css'; import { localize } from '../../../../nls.js'; import { getActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; @@ -70,13 +70,14 @@ interface IContextualResourceMenuItem { class SCMMenusItem implements IDisposable { - private _resourceGroupMenu: IMenu | undefined; + private readonly _resourceGroupMenu = new MutableDisposable(); get resourceGroupMenu(): IMenu { - if (!this._resourceGroupMenu) { - this._resourceGroupMenu = this.menuService.createMenu(MenuId.SCMResourceGroupContext, this.contextKeyService); - } + const contextKeyService = this.contextKeyService.createOverlay([ + ['scmResourceGroupResourceCount', this.group.resources.length], + ]); - return this._resourceGroupMenu; + this._resourceGroupMenu.value = this.menuService.createMenu(MenuId.SCMResourceGroupContext, contextKeyService); + return this._resourceGroupMenu.value; } private _resourceFolderMenu: IMenu | undefined; @@ -92,8 +93,9 @@ class SCMMenusItem implements IDisposable { private contextualResourceMenus: Map | undefined; constructor( - private contextKeyService: IContextKeyService, - private menuService: IMenuService + private readonly group: ISCMResourceGroup, + private readonly contextKeyService: IContextKeyService, + private readonly menuService: IMenuService ) { } getResourceMenu(resource: ISCMResource): IMenu { @@ -206,7 +208,7 @@ export class SCMRepositoryMenus implements ISCMRepositoryMenus, IDisposable { ['multiDiffEditorEnableViewChanges', group.multiDiffEditorEnableViewChanges], ]); - result = new SCMMenusItem(contextKeyService, this.menuService); + result = new SCMMenusItem(group, contextKeyService, this.menuService); this.resourceGroupMenusItems.set(group, result); } From 2e5cbd49c8252d6e1dc44de2ce8f63899a8774f7 Mon Sep 17 00:00:00 2001 From: Robo Date: Mon, 23 Dec 2024 19:22:49 +0900 Subject: [PATCH 198/200] chore: update electron@32.2.7 (#236843) * chore: update electron@32.2.7 * chore: bump distro --- .npmrc | 4 +- build/checksums/electron.txt | 150 +++++++++++++++++------------------ cgmanifest.json | 4 +- package-lock.json | 8 +- package.json | 4 +- 5 files changed, 85 insertions(+), 85 deletions(-) diff --git a/.npmrc b/.npmrc index 22256e5d8e7ba..27692c2409eec 100644 --- a/.npmrc +++ b/.npmrc @@ -1,6 +1,6 @@ disturl="https://electronjs.org/headers" -target="32.2.6" -ms_build_id="10629634" +target="32.2.7" +ms_build_id="10660205" runtime="electron" build_from_source="true" legacy-peer-deps="true" diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index 17ec96faa2a02..293250496af85 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,75 +1,75 @@ -bb4164f7b554606b2c4daaf43e81bf2e2b5cf0d4441cfdd74f04653237fcf655 *chromedriver-v32.2.6-darwin-arm64.zip -a0fc3df1c6cd17bfe62ffbb1eba3655ca625dea5046e5d2b3dbb0e9e349cd10e *chromedriver-v32.2.6-darwin-x64.zip -671d6dab890747ea73ba5589327eef7612670950a20e5f88c7d8a301b5491e26 *chromedriver-v32.2.6-linux-arm64.zip -55bfd4e33fef1506261d4cb3074988e1970c2a762ca76a8f1197512a1766723c *chromedriver-v32.2.6-linux-armv7l.zip -d3c7a45c8c75152db927b3596f506995e72631df870b302b7dbcbd3399e54a3a *chromedriver-v32.2.6-linux-x64.zip -567f77d09708942901c6cdce6708b995f6ac779faceebb4ed383ca5003e2de4e *chromedriver-v32.2.6-mas-arm64.zip -b3a28181b1d077742f1be632a802e15b5a36a260b1cfe0e429735de9f52d074a *chromedriver-v32.2.6-mas-x64.zip -a113f5bd747b6eeb033f4d6ea2f53cf332d9b45d6340af514dd938bac7f99419 *chromedriver-v32.2.6-win32-arm64.zip -3b3237a788fad0a6be63a69b93c28b6052db23aeaa1a75d2589be15b4c2c0f2f *chromedriver-v32.2.6-win32-ia32.zip -1096c131cf8e3f98a01525e93d573eaf4fd23492d8dd78a211e39c448e69e463 *chromedriver-v32.2.6-win32-x64.zip -8e6fcf3171c3fcdcb117f641ec968bb53be3d38696e388636bf34f04c10b987d *electron-api.json -b1b20784a97e64992c92480e69af828a110d834372479b26759f1559b3da80fc *electron-v32.2.6-darwin-arm64-dsym-snapshot.zip -f11dd5a84229430ec59b4335415a4b308dc4330ff7b9febae20165fbdd862e92 *electron-v32.2.6-darwin-arm64-dsym.zip -cc96cf91f6b108dc927d8f7daee2fe27ae8a492c932993051508aa779e816445 *electron-v32.2.6-darwin-arm64-symbols.zip -fcb6bbb6aa3c1020b4045dbe9f2a5286173d5025248550f55631e70568e91775 *electron-v32.2.6-darwin-arm64.zip -d2bfeea27fc91936b4f71d0f5c577e5ad0ea094edba541dfa348948fd65c3331 *electron-v32.2.6-darwin-x64-dsym-snapshot.zip -5e7684cc12c0dee11fb933b68301d0fe68d3198d1daeadd5e1b4cf52743f79bf *electron-v32.2.6-darwin-x64-dsym.zip -6d2a7d41ab14fc7d3c5e4b35d5d425edb2d13978dcc332e781ec8b7bcfe6a794 *electron-v32.2.6-darwin-x64-symbols.zip -c0964ee5fdcefb1003ffd7ef574b07e5147856f3a94bb4335f78c409f8cf2eca *electron-v32.2.6-darwin-x64.zip -604d88b9d434ea66ddf234dd129dcef3d468b95b0da47e2f1555a682c8d0de28 *electron-v32.2.6-linux-arm64-debug.zip -f448e91df42fc84177bcd46378e49ee648f6114984fc57af4a84690a5197c23e *electron-v32.2.6-linux-arm64-symbols.zip -9e4f9345cae06e8e5679b228e7b7ac21b8733e3fcda8903e3dcbc8171c53f8be *electron-v32.2.6-linux-arm64.zip -604d88b9d434ea66ddf234dd129dcef3d468b95b0da47e2f1555a682c8d0de28 *electron-v32.2.6-linux-armv7l-debug.zip -c85d5ca3f38dc4140040bcde6a37ac9c7510bb542f12e1ffce695a35f68e3c13 *electron-v32.2.6-linux-armv7l-symbols.zip -df4b490a9c501d83c5305f20b2a9d1aa100d2e878e59ebafde477f21d35e3300 *electron-v32.2.6-linux-armv7l.zip -a5aa67da85ee318ff0770d55a506f62e6e5a10e967dfab272a94bcd91922e075 *electron-v32.2.6-linux-x64-debug.zip -671eb342a58e056f0dee5a6e9c69a6a96ee2141f81d306fa1a0e2635e22aa7c0 *electron-v32.2.6-linux-x64-symbols.zip -a3231409db7f8ac2cc446708f17e2abac0f8549c166eaab2f427e8d0f864208d *electron-v32.2.6-linux-x64.zip -8b8d0aeadcf21633216a9cce87d323ad6aa21e38ec82092cd5f65bf171be025f *electron-v32.2.6-mas-arm64-dsym-snapshot.zip -298931236955b83d174738d3325931e9672a8333bf854fc5f471168b0f7e70be *electron-v32.2.6-mas-arm64-dsym.zip -53e4c666a6f5f87aa150b1c2f34532e3711e00b0237fb103dcbef64d65979429 *electron-v32.2.6-mas-arm64-symbols.zip -bedbc78acc3bc6cb30e5fe1f133562104152022273ec21a83cd32a0eece9003f *electron-v32.2.6-mas-arm64.zip -9ba3def756c90460867d968af5417996991bf3b4b306dba4c9fbde43de2f771d *electron-v32.2.6-mas-x64-dsym-snapshot.zip -e4a3f9392934bb4ef82a214fec2ce1f319ea409b89bf03b2a2a4ab7a55688d0c *electron-v32.2.6-mas-x64-dsym.zip -55c54fd01faf5767493183001a440b837b63f8b8d64c36f7b42fa7217af36dcd *electron-v32.2.6-mas-x64-symbols.zip -1efe974cd426a288d617750c864e6edbf28495c3b851a5bc806af19cda7a274d *electron-v32.2.6-mas-x64.zip -88c50d34dc48a55a11014349d2d278f895f1615405614dbfcf27dff5f5030cf1 *electron-v32.2.6-win32-arm64-pdb.zip -b0b2211bf0f11924bcd693b6783627c7f6c9a066117bcf05c10766064c79794c *electron-v32.2.6-win32-arm64-symbols.zip -48b81d28fdceb4ab3ca27650d79bab910a1a19dbda72271882bfdc877c71975f *electron-v32.2.6-win32-arm64-toolchain-profile.zip -2b7962348f23410863cb6562d79654ce534666bab9f75965b5c8ebee61f49657 *electron-v32.2.6-win32-arm64.zip -d248ab4ec8b4a5aec414c7a404e0efd9b9a73083f7c5cb6c657c2ed58c4cbe94 *electron-v32.2.6-win32-ia32-pdb.zip -2ef98197d66d94aee4978047f22ba07538d259b25a8f5f301d9564a13649e72c *electron-v32.2.6-win32-ia32-symbols.zip -48b81d28fdceb4ab3ca27650d79bab910a1a19dbda72271882bfdc877c71975f *electron-v32.2.6-win32-ia32-toolchain-profile.zip -e85dbf85d58cab5b06cdb8e76fde3a25306e7c1808f5bb30925ba7e29ff14d13 *electron-v32.2.6-win32-ia32.zip -9d76b0c0d475cc062b2a951fbfb3b17837880102fb6cc042e2736dfebbfa0e5d *electron-v32.2.6-win32-x64-pdb.zip -3d7b93bafc633429f5c9f820bcb4843d504b12e454b3ecebb1b69c15b5f4080f *electron-v32.2.6-win32-x64-symbols.zip -48b81d28fdceb4ab3ca27650d79bab910a1a19dbda72271882bfdc877c71975f *electron-v32.2.6-win32-x64-toolchain-profile.zip -77d5e5b76b49767e6a3ad292dc315fbc7cdccd557ac38da9093b8ac6da9262d5 *electron-v32.2.6-win32-x64.zip -a52935712eb4fc2c12ac4ae611a57e121da3b6165c2de1abd7392ed4261287e2 *electron.d.ts -6345ea55fda07544434c90c276cdceb2662044b9e0355894db67ca95869af22a *ffmpeg-v32.2.6-darwin-arm64.zip -4c1347e8653727513a22be013008c2760d19200977295b98506b3b9947e74090 *ffmpeg-v32.2.6-darwin-x64.zip -3f1eafaf4cd90ab43ba0267429189be182435849a166a2cbe1faefc0d07217c4 *ffmpeg-v32.2.6-linux-arm64.zip -3db919bc57e1a5bf7c1bae1d7aeacf4a331990ea82750391c0b24a046d9a2812 *ffmpeg-v32.2.6-linux-armv7l.zip -fe7d779dddbfb5da5999a7607fc5e3c7a6ab7c65e8da9fee1384918865231612 *ffmpeg-v32.2.6-linux-x64.zip -e09ae881113d1b3103aec918e7c95c36f82b2db63657320c380c94386f689138 *ffmpeg-v32.2.6-mas-arm64.zip -ee316e435662201a81fcededc62582dc87a0bd5c9fd0f6a8a55235eca806652f *ffmpeg-v32.2.6-mas-x64.zip -415395968d31e13056cefcb589ed550a0e80d7c3d0851ee3ba29e4dcdf174210 *ffmpeg-v32.2.6-win32-arm64.zip -17f61a5293b707c984cee52b57a7e505cde994d22828e31c016434521638e419 *ffmpeg-v32.2.6-win32-ia32.zip -f9b47951a553eec21636b3cc15eccf0a2ba272567146ec8a6e2eeb91a985fd62 *ffmpeg-v32.2.6-win32-x64.zip -7d2b596bd94e4d5c7befba11662dc563a02f18c183da12ebd56f38bb6f4382e9 *hunspell_dictionaries.zip -06bca9a33142b5834fbca6d10d7f3303b0b5c52e7222fe783db109cd4c5260ed *libcxx-objects-v32.2.6-linux-arm64.zip -7ab8ff5a4e1d3a6639978ed718d2732df9c1a4dd4dcd3e18f6746a2168d353a9 *libcxx-objects-v32.2.6-linux-armv7l.zip -ba96792896751e11fdba63e5e336e323979986176aa1848122ca3757c854352e *libcxx-objects-v32.2.6-linux-x64.zip -1f858d484f4ce27f9f3c4a120b2881f88c17c81129ca10e495b50398fb2eed64 *libcxx_headers.zip -4566afb06a6dd8bd895dba5350a5705868203321116a1ae0a216d5a4a6bfb289 *libcxxabi_headers.zip -350f5419c14aede5802c4f0ef5204ddbbe0eb7d5263524da38d19f43620d8530 *mksnapshot-v32.2.6-darwin-arm64.zip -9f4e4943df4502943994ffa17525b7b5b9a1d889dbc5aeb28bfc40f9146323ec *mksnapshot-v32.2.6-darwin-x64.zip -6295ad1a4ab3b24ac99ec85d35ebfce3861e58185b94d20077e364e81ad935f8 *mksnapshot-v32.2.6-linux-arm64-x64.zip -ed9e1931165a2ff85c1af9f10b3bf8ba05d2dae31331d1d4f5ff1512078e4411 *mksnapshot-v32.2.6-linux-armv7l-x64.zip -6680c63b11e4638708d88c130474ceb2ee92fc58c62cfcd3bf33dda9fee771c2 *mksnapshot-v32.2.6-linux-x64.zip -5a4c45b755b7bbdcad51345f5eb2935ba988987650f9b8540c7ef04015207a2f *mksnapshot-v32.2.6-mas-arm64.zip -1e6243d6a1b68f327457b9dae244ffaeadc265b07a99d9c36f1abcff31e36856 *mksnapshot-v32.2.6-mas-x64.zip -407099537b17860ce7dcea6ba582a854314c29dc152a6df0abcc77ebb0187b4c *mksnapshot-v32.2.6-win32-arm64-x64.zip -f9107536378e19ae9305386dacf6fae924c7ddaad0cf0a6f49694e1e8af6ded5 *mksnapshot-v32.2.6-win32-ia32.zip -82a142db76a2cc5dfb9a89e4cd721cfebc0f3077d2415d8f3d86bb7411f0fe27 *mksnapshot-v32.2.6-win32-x64.zip +0729d2cb830425c4591b40d7189c2f7da020f5adb887a49a4faa022d551b1e6f *chromedriver-v32.2.7-darwin-arm64.zip +a7d61c68d3b3522c0ec383915b6ab3d9f269d9ada0e09aa87a4e7d9fc24fe928 *chromedriver-v32.2.7-darwin-x64.zip +45314c8c7127f6083469c2c067aa78beb20055ba4d7a63eb2108b27e594a647a *chromedriver-v32.2.7-linux-arm64.zip +7e976a7131dcfd55f781c073ae59c8a24a1393119d831fbac13c6a335eb71467 *chromedriver-v32.2.7-linux-armv7l.zip +3437feb5d8e7157476d2e7a6558346061cd7e46506874bc7870eed8a3a43642a *chromedriver-v32.2.7-linux-x64.zip +3737301add80a936374acb17b84bb3a715fab9fbce049816ea7a51fa53d3b35e *chromedriver-v32.2.7-mas-arm64.zip +8b8b62f48a5e8b8a340b47348a2cc5dd4ba38789f76bc5567c039587538081a9 *chromedriver-v32.2.7-mas-x64.zip +24b666e3ab41eb1c66ab0f2361af0529b2b8e1e5ef153cfcef36adc700f9ed6d *chromedriver-v32.2.7-win32-arm64.zip +6d40251661afb1835adbef85e317fd520c0f1378156614c82befb348c3122c2d *chromedriver-v32.2.7-win32-ia32.zip +483012da9903d8d75e5e251a3262667c9a0a012a492b93dbe1237c7827eba778 *chromedriver-v32.2.7-win32-x64.zip +1e9b2b9011f56fa26f4d9fa57254ef1d0bdb34405a9bdf83a652f6258347e46d *electron-api.json +c0ea4a21f2e7e946300bf587a4e31f72ef996c497eaa94e6b8f788b917b896e5 *electron-v32.2.7-darwin-arm64-dsym-snapshot.zip +1e6e84e56cfb3a2473cab41577c160d3afcbda8337dda17c5295da90266433c9 *electron-v32.2.7-darwin-arm64-dsym.zip +9f460100fb71ef098bec26e9a09978ec1b1663165d6a358bfc398f5548a844c3 *electron-v32.2.7-darwin-arm64-symbols.zip +71e76a0a81a0c1c10e9e4862caf96437ba85a18c8fa7d8e15d59e3c057b893bd *electron-v32.2.7-darwin-arm64.zip +e406d690365f332826843c86c6a1b5c0320a84b0527ad8700a0e995b12a35f8c *electron-v32.2.7-darwin-x64-dsym-snapshot.zip +53d6fb64d717af80f024284161a432aaffb47631ef7548f18f33016c3376871a *electron-v32.2.7-darwin-x64-dsym.zip +3c9187db2cc0570d7b01651fa78294df7d451c87c361335cee80a61c1c561b67 *electron-v32.2.7-darwin-x64-symbols.zip +34310ed51d32b6c02ba3e3f447b0807ea85804d1f2b239e02a9de58b9080fbf8 *electron-v32.2.7-darwin-x64.zip +e884f2f9f3d001488888929b8affe053a60a7a780af7d0ec8d7023023f40ca52 *electron-v32.2.7-linux-arm64-debug.zip +fd88e47e7b564b006f68641b5c328721bbc8d87cfc9e569d9733354d263cddee *electron-v32.2.7-linux-arm64-symbols.zip +fb6e1f24385c3058844bd768320d5b332b4cbd011ab930e7252dc330c8ee17b3 *electron-v32.2.7-linux-arm64.zip +e884f2f9f3d001488888929b8affe053a60a7a780af7d0ec8d7023023f40ca52 *electron-v32.2.7-linux-armv7l-debug.zip +f338ea7ea592c3ccdad1bb788e9b936610f825ac69290e48d394790d5266dce3 *electron-v32.2.7-linux-armv7l-symbols.zip +4a95643e88cadfb011354d25cafe242cdb8c5327d65f86b4fbabe64717367ed2 *electron-v32.2.7-linux-armv7l.zip +e6e0fce9f6d95a84653b537b741967cae48c4c70c8026c02293c979074225b46 *electron-v32.2.7-linux-x64-debug.zip +122cc565d0ccd2774e298645473869752d27d2632aa97583d93b499e9b02f22b *electron-v32.2.7-linux-x64-symbols.zip +98007545e1d3700b32de5cb5eebcc10b9d105fb0dad6396155fdab1b40abb638 *electron-v32.2.7-linux-x64.zip +556d9ca239ee1206c9d67affa836ebb651db88eea6bee48cb7b43fa75851c72d *electron-v32.2.7-mas-arm64-dsym-snapshot.zip +662a3742b94fcbf7ab91a7c20e1430825ae7852e915fcb558d6357a310d631c6 *electron-v32.2.7-mas-arm64-dsym.zip +edd0763ead7ffd5bf5072539e5ca0be9252b9590e674e6e44e69b2057c329d79 *electron-v32.2.7-mas-arm64-symbols.zip +a4483f5246ecadfa48b1fc671d92b5dfbc09fbd88fe386f2ce48f10de79f2127 *electron-v32.2.7-mas-arm64.zip +a9aad4c413d4851fa3463eeef7015e3a3e77a501192965db1c5b870fa31a9660 *electron-v32.2.7-mas-x64-dsym-snapshot.zip +96c20e5c4b73febd3458679e9cc939f5f8255a327b06f49188ab2e3fe8311ea3 *electron-v32.2.7-mas-x64-dsym.zip +6ac844957373114e04411d3af1cb6507e35174d1dc279cce41cb92bbf2ea5d26 *electron-v32.2.7-mas-x64-symbols.zip +888b830b991dab6cf2c4351e112a48f24a4748efefcd763d693a79161199e65a *electron-v32.2.7-mas-x64.zip +27759db6bcdd16d4ff5548684361ba4372d885d3142bf02db59837c3634b1934 *electron-v32.2.7-win32-arm64-pdb.zip +6019e6ec58e9b6da335f20874efebc42d034a179163180b3b6faedf2963ae577 *electron-v32.2.7-win32-arm64-symbols.zip +48b81d28fdceb4ab3ca27650d79bab910a1a19dbda72271882bfdc877c71975f *electron-v32.2.7-win32-arm64-toolchain-profile.zip +2c755fdd4f9fda618b2db6b8c7210c5f3106a88b1e87b83e8433b4ab4a628cc2 *electron-v32.2.7-win32-arm64.zip +4dce0b21d1c2093cc4f7c0eaf9453a38377e0076d811da3c7391f105fc1d6afb *electron-v32.2.7-win32-ia32-pdb.zip +9a0a9c3746cd40ddc9c926755633b16676714e2138d7a2d888f658a26f617039 *electron-v32.2.7-win32-ia32-symbols.zip +48b81d28fdceb4ab3ca27650d79bab910a1a19dbda72271882bfdc877c71975f *electron-v32.2.7-win32-ia32-toolchain-profile.zip +6c338c5cd0b0587349ab0f119ca8f7d2728b1c3a43fe241741087f5fdf139c9c *electron-v32.2.7-win32-ia32.zip +fa240d324c5376aa12ed2aef26597764d9bfc2fdd0d16d7f76afc2c3e3c65a29 *electron-v32.2.7-win32-x64-pdb.zip +f645b53771cbcdfaa041d9cf9581348821d82c1b185ddb913759e2d62ee2410a *electron-v32.2.7-win32-x64-symbols.zip +48b81d28fdceb4ab3ca27650d79bab910a1a19dbda72271882bfdc877c71975f *electron-v32.2.7-win32-x64-toolchain-profile.zip +819ab19b7111dfd39dff506b3cb5cd2e1d8f4bb17f96ba74b987b2eac14b6c63 *electron-v32.2.7-win32-x64.zip +ce41b10c28bd43249cd3b409e081b1c83a2b691381bdd2e3bf208ec40ca176b8 *electron.d.ts +d2491071a641ce2e0f63c1f52e3a412856dd83ca17d021af1166d6e5b4de5638 *ffmpeg-v32.2.7-darwin-arm64.zip +5c5589b2c93f834e595eb692aa768b934245d2631df69bc4cad3a6602bba0e67 *ffmpeg-v32.2.7-darwin-x64.zip +3f1eafaf4cd90ab43ba0267429189be182435849a166a2cbe1faefc0d07217c4 *ffmpeg-v32.2.7-linux-arm64.zip +3db919bc57e1a5bf7c1bae1d7aeacf4a331990ea82750391c0b24a046d9a2812 *ffmpeg-v32.2.7-linux-armv7l.zip +fe7d779dddbfb5da5999a7607fc5e3c7a6ab7c65e8da9fee1384918865231612 *ffmpeg-v32.2.7-linux-x64.zip +feeef1ab10543c813f730cc7a482b43eda35d40f1285b950e1a6d7805db2332a *ffmpeg-v32.2.7-mas-arm64.zip +96ef45180589c854fedf2d0601a20e70a65220c0820c45d0dfd4ec64724c58e0 *ffmpeg-v32.2.7-mas-x64.zip +ab4ab9cd62e40c4d3064004caa9de680cb72d8180d4facc1be06bdc886c23410 *ffmpeg-v32.2.7-win32-arm64.zip +90b5e2ebd4ff683eda97cc43ebbdee9b133b27edd2a34ae7ef37e7969d1d68be *ffmpeg-v32.2.7-win32-ia32.zip +8452085c0a650035f30a4b76e2ce1791f9b392ea7262109d29f7fe383fc41ddb *ffmpeg-v32.2.7-win32-x64.zip +78b415ebb9040dacabb6eb776a8d4837dda9a9b1ec9d64ee15db28dbb8598862 *hunspell_dictionaries.zip +a30057c37e6be5732944084575a2278616297242ae51bd474c683263cbc0c3e4 *libcxx-objects-v32.2.7-linux-arm64.zip +f9e9d1ff1a03a3e609ab8e727b1f89e77934509a4afdb849698b70e701c2176f *libcxx-objects-v32.2.7-linux-armv7l.zip +bb66e3b48f8e0706126b2b8b08827a4adda6f56c509eae4d136fcffd5414c353 *libcxx-objects-v32.2.7-linux-x64.zip +5181518d7da83fea5d8b033ab4fb7ed300f73bd8d20b8c26b624128233bd6ab2 *libcxx_headers.zip +6030ad099859b62cbdd9021b2cdb453a744a2751cb1dab30519e3e8708ad72d6 *libcxxabi_headers.zip +d3dcc4925a6bd55bc305fd41805ffee77dc8821730ac75cf4ee9ed2ca4ebdccb *mksnapshot-v32.2.7-darwin-arm64.zip +e6dfad3c30f4f38509b2fc972dd05cef06142c4832d931edba19742e06161279 *mksnapshot-v32.2.7-darwin-x64.zip +25ba5be47a721700f16af10945e71408ed86ffd6800b5d5ef04d38c0d77aa446 *mksnapshot-v32.2.7-linux-arm64-x64.zip +f7e8b50691712206587d81844bd63271f2dd49253c946a5b66bd6f169ccf94d6 *mksnapshot-v32.2.7-linux-armv7l-x64.zip +a0b119abe93c0231601b6c699cce4b78e89def766c24f9a8a06cfab3feca8f6c *mksnapshot-v32.2.7-linux-x64.zip +e3e8a496a1eaf6c8ce623fa4b139e5458cf3ce3702ea3560cded839087b60792 *mksnapshot-v32.2.7-mas-arm64.zip +c03219273c82022c29e277d07ce1d0980d25c22d39269fa3eef9547f57ec410b *mksnapshot-v32.2.7-mas-x64.zip +7684cb9c6f621db05b6e68080fade81f46d0ff8eeac94080bd635f035069d13e *mksnapshot-v32.2.7-win32-arm64-x64.zip +f7ca1d557e3d0f878b13f57dc0e00932f7a97f3dd0f0cc3bbbd565a06718bd17 *mksnapshot-v32.2.7-win32-ia32.zip +d9d8dd33561eb648e5ebd00f99418122d9a915ec63fe967e7cb0ff64ef8ee199 *mksnapshot-v32.2.7-win32-x64.zip diff --git a/cgmanifest.json b/cgmanifest.json index 832a3f604b782..1795c44c93724 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -528,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "10e0ce069260b2cc984c301d5a7ecb3492f0c2f0" + "commitHash": "3007f859dad930ae80bafffc6042a146a45e4e4d" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "32.2.6" + "version": "32.2.7" }, { "component": { diff --git a/package-lock.json b/package-lock.json index 5f2b14c3e3973..23a24c728bdd0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -95,7 +95,7 @@ "cssnano": "^6.0.3", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "32.2.6", + "electron": "32.2.7", "eslint": "^9.11.1", "eslint-formatter-compact": "^8.40.0", "eslint-plugin-header": "3.1.1", @@ -6103,9 +6103,9 @@ "dev": true }, "node_modules/electron": { - "version": "32.2.6", - "resolved": "https://registry.npmjs.org/electron/-/electron-32.2.6.tgz", - "integrity": "sha512-aGG1MLvWCf+ECUFBCmaCF52F8312OPAJfph2D0FSsFmlbfnJuNevZCbty2lFzsiIMtU7/QRo6d0ksbgR4s7y3w==", + "version": "32.2.7", + "resolved": "https://registry.npmjs.org/electron/-/electron-32.2.7.tgz", + "integrity": "sha512-y8jbQRG3xogF70XPlk5c+dWe5iRfUBo28o2NMpKd/CcW7ENIaWtBlGima8/8nmRdAaYTy1+yIt6KB0Lon9H8cA==", "dev": true, "hasInstallScript": true, "license": "MIT", diff --git a/package.json b/package.json index 3316ae225ea89..ba5004eed3e15 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.97.0", - "distro": "45ffe59ad7c51acf28b541a2aa76cc73437ff118", + "distro": "baf3347105f6082ae1df942fd1c052cd06f7a7f0", "author": { "name": "Microsoft Corporation" }, @@ -153,7 +153,7 @@ "cssnano": "^6.0.3", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "32.2.6", + "electron": "32.2.7", "eslint": "^9.11.1", "eslint-formatter-compact": "^8.40.0", "eslint-plugin-header": "3.1.1", From fca210cd103a496f25c23786b861a67f4d1ee16b Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 23 Dec 2024 12:56:24 +0100 Subject: [PATCH 199/200] Git - escape shell-sensitive characters (#236849) --- extensions/git/src/git.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 62bae1422df0b..5bdfd655dbc41 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -348,10 +348,15 @@ function getGitErrorCode(stderr: string): string | undefined { return undefined; } -// https://github.com/microsoft/vscode/issues/89373 -// https://github.com/git-for-windows/git/issues/2478 function sanitizePath(path: string): string { - return path.replace(/^([a-z]):\\/i, (_, letter) => `${letter.toUpperCase()}:\\`); + return path + // Drive letter + // https://github.com/microsoft/vscode/issues/89373 + // https://github.com/git-for-windows/git/issues/2478 + .replace(/^([a-z]):\\/i, (_, letter) => `${letter.toUpperCase()}:\\`) + // Shell-sensitive characters + // https://github.com/microsoft/vscode/issues/133566 + .replace(/(["'\\\$!><#()\[\]*&^| ;{}?`])/g, '\\$1'); } const COMMIT_FORMAT = '%H%n%aN%n%aE%n%at%n%ct%n%P%n%D%n%B'; From 4fa5611d67dc84e105e9cd155a746f2d7813d9a0 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 24 Dec 2024 02:02:40 +0100 Subject: [PATCH 200/200] Git - handle the diff editor for untracked files now that we throw `FileNotFound` if the file does not exist (#236863) --- extensions/git/src/fileSystemProvider.ts | 3 ++- extensions/git/src/repository.ts | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/fileSystemProvider.ts b/extensions/git/src/fileSystemProvider.ts index 24ae4e6df9a71..0847fe8d745a2 100644 --- a/extensions/git/src/fileSystemProvider.ts +++ b/extensions/git/src/fileSystemProvider.ts @@ -192,7 +192,8 @@ export class GitFileSystemProvider implements FileSystemProvider { try { return await repository.buffer(sanitizeRef(ref, path, repository), path); } catch (err) { - // File does not exist in git (ex: git ignored) + // File does not exist in git. This could be + // because the file is untracked or ignored throw FileSystemError.FileNotFound(); } } diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index ec66d510c720d..4bf7fa32ef0e9 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -560,13 +560,11 @@ class ResourceCommandResolver { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_RENAMED: - case Status.INDEX_ADDED: case Status.INTENT_TO_RENAME: case Status.TYPE_CHANGED: return { original: toGitUri(resource.original, 'HEAD') }; case Status.MODIFIED: - case Status.UNTRACKED: return { original: toGitUri(resource.resourceUri, '~') }; case Status.DELETED_BY_US: