From f21001c1fecfd966a76ff02fd772dd823ed1d46f Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 14 Jul 2023 17:36:58 +0200 Subject: [PATCH 01/69] wip --- .../api/browser/extensionHost.contribution.ts | 2 + .../api/browser/mainThreadChatProvider.ts | 53 ++++ .../browser/mainThreadChatSlashCommands.ts | 51 ++++ .../workbench/api/common/extHost.api.impl.ts | 19 ++ .../workbench/api/common/extHost.protocol.ts | 26 ++ .../api/common/extHostChatProvider.ts | 69 +++++ .../api/common/extHostChatSlashCommand.ts | 70 ++++++ .../api/common/extHostTypeConverters.ts | 21 ++ src/vs/workbench/api/common/extHostTypes.ts | 19 ++ .../contrib/chat/browser/chat.contribution.ts | 5 +- .../contrib/chat/common/chatProvider.ts | 68 +++++ .../contrib/chat/common/chatSlashCommands.ts | 236 ++++++++++++++++++ .../common/extensionsApiProposals.ts | 2 + .../vscode.proposed.chatProvider.d.ts | 46 ++++ .../vscode.proposed.chatSlashCommands.d.ts | 45 ++++ 15 files changed, 731 insertions(+), 1 deletion(-) create mode 100644 src/vs/workbench/api/browser/mainThreadChatProvider.ts create mode 100644 src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts create mode 100644 src/vs/workbench/api/common/extHostChatProvider.ts create mode 100644 src/vs/workbench/api/common/extHostChatSlashCommand.ts create mode 100644 src/vs/workbench/contrib/chat/common/chatProvider.ts create mode 100644 src/vs/workbench/contrib/chat/common/chatSlashCommands.ts create mode 100644 src/vscode-dts/vscode.proposed.chatProvider.d.ts create mode 100644 src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index d582b31b0b0..6328cfd2df9 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -19,6 +19,8 @@ import { StatusBarItemsExtensionPoint } from 'vs/workbench/api/browser/statusBar // --- mainThread participants import './mainThreadLocalization'; import './mainThreadBulkEdits'; +import './mainThreadChatProvider'; +import './mainThreadChatSlashCommands'; import './mainThreadCodeInsets'; import './mainThreadCLICommands'; import './mainThreadClipboard'; diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts new file mode 100644 index 00000000000..bdb067ec1d8 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableMap } from 'vs/base/common/lifecycle'; +import { IProgress } from 'vs/platform/progress/common/progress'; +import { ExtHostChatProviderShape, ExtHostContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IChatResponseProviderMetadata, IChatResponseFragment, IChatProviderService } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; + +@extHostNamedCustomer(MainContext.MainThreadChatProvider) +export class MainThreadChatProvider implements MainThreadChatProviderShape { + + private readonly _proxy: ExtHostChatProviderShape; + private readonly _providerRegistrations = new DisposableMap(); + private readonly _pendingProgress = new Map>(); + + constructor( + extHostContext: IExtHostContext, + @IChatProviderService private readonly _chatProviderService: IChatProviderService, + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider); + } + + dispose(): void { + this._providerRegistrations.dispose(); + } + + $registerProvider(handle: number, metadata: IChatResponseProviderMetadata): void { + const registration = this._chatProviderService.registerChatResponseProvider({ + metadata, + provideChatResponse: async (messages, options, progress, token) => { + const requestId = (Math.random() * 1e6) | 0; + this._pendingProgress.set(requestId, progress); + try { + await this._proxy.$provideChatResponse(handle, requestId, messages, options, token); + } finally { + this._pendingProgress.delete(requestId); + } + } + }); + this._providerRegistrations.set(handle, registration); + } + + async $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise { + this._pendingProgress.get(requestId)?.report(chunk); + } + + $unregisterProvider(handle: number): void { + this._providerRegistrations.deleteAndDispose(handle); + } +} diff --git a/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts b/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts new file mode 100644 index 00000000000..839885602ed --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableMap } from 'vs/base/common/lifecycle'; +import { IProgress } from 'vs/platform/progress/common/progress'; +import { ExtHostChatSlashCommandsShape, ExtHostContext, MainContext, MainThreadChatSlashCommandsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IChatSlashCommandService, IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; +import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; + + +@extHostNamedCustomer(MainContext.MainThreadChatSlashCommands) +export class MainThreadChatSlashCommands implements MainThreadChatSlashCommandsShape { + + private readonly _commands = new DisposableMap; + private readonly _pendingProgress = new Map>(); + private readonly _proxy: ExtHostChatSlashCommandsShape; + + constructor( + extHostContext: IExtHostContext, + @IChatSlashCommandService private readonly _chatSlashCommandService: IChatSlashCommandService + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatSlashCommands); + } + + dispose(): void { + this._commands.clearAndDisposeAll(); + } + + $registerCommand(handle: number, name: string): void { + const d = this._chatSlashCommandService.registerSlashCallback(name, async (prompt, progress, history, token) => { + const requestId = Math.random(); + this._pendingProgress.set(requestId, progress); + try { + await this._proxy.$executeCommand(handle, requestId, prompt, { history }, token); + } finally { + this._pendingProgress.delete(requestId); + } + }); + this._commands.set(handle, d); + } + + async $handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise { + this._pendingProgress.get(requestId)?.report(chunk); + } + + $unregisterCommand(handle: number): void { + this._commands.deleteAndDispose(handle); + } +} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 1e585c0d3b0..14a51200438 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -104,6 +104,8 @@ import { ExtHostSemanticSimilarity } from 'vs/workbench/api/common/extHostSemant import { ExtHostIssueReporter } from 'vs/workbench/api/common/extHostIssueReporter'; import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets'; import { ExtHostShare } from 'vs/workbench/api/common/extHostShare'; +import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; +import { ExtHostChatSlashCommands } from 'vs/workbench/api/common/extHostChatSlashCommand'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -203,6 +205,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostProfileContentHandlers = rpcProtocol.set(ExtHostContext.ExtHostProfileContentHandlers, new ExtHostProfileContentHandlers(rpcProtocol)); rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); + const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService)); + const extHostChatSlashCommands = rpcProtocol.set(ExtHostContext.ExtHostChatSlashCommands, new ExtHostChatSlashCommands(rpcProtocol, extHostChatProvider, extHostLogService)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol, extHostLogService)); const extHostSemanticSimilarity = rpcProtocol.set(ExtHostContext.ExtHostSemanticSimilarity, new ExtHostSemanticSimilarity(rpcProtocol)); const extHostIssueReporter = rpcProtocol.set(ExtHostContext.ExtHostIssueReporter, new ExtHostIssueReporter(rpcProtocol)); @@ -1324,6 +1328,18 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } }; + // namespace: llm + const llm: typeof vscode.llm = { + registerChatResponseProvider(provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata) { + checkProposedApiEnabled(extension, 'chatProvider'); + return extHostChatProvider.registerProvider(extension.identifier, provider, metadata); + }, + registerSlashCommand(name: string, command: vscode.SlashCommand) { + checkProposedApiEnabled(extension, 'chatSlashCommands'); + return extHostChatSlashCommands.registerCommand(extension.identifier, name, command); + } + }; + return { version: initData.version, // namespaces @@ -1337,6 +1353,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I interactive, interactiveSlashCommands, l10n, + llm, languages, notebooks, scm, @@ -1347,6 +1364,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // types Breakpoint: extHostTypes.Breakpoint, TerminalOutputAnchor: extHostTypes.TerminalOutputAnchor, + ChatMessage: extHostTypes.ChatMessage, + ChatMessageRole: extHostTypes.ChatMessageRole, CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall, CallHierarchyItem: extHostTypes.CallHierarchyItem, CallHierarchyOutgoingCall: extHostTypes.CallHierarchyOutgoingCall, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5320cb4a8b0..cf713f31a88 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -74,6 +74,8 @@ import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/qu import * as search from 'vs/workbench/services/search/common/search'; import { ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { TerminalCommandMatchResult, TerminalQuickFixCommand, TerminalQuickFixOpener } from 'vscode'; +import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; export type TerminalQuickFix = TerminalQuickFixCommand | TerminalQuickFixOpener; @@ -1119,6 +1121,26 @@ export interface MainThreadNotebookRenderersShape extends IDisposable { export interface MainThreadInteractiveShape extends IDisposable { } +export interface MainThreadChatProviderShape extends IDisposable { + $registerProvider(handle: number, metadata: IChatResponseProviderMetadata): void; + $unregisterProvider(handle: number): void; + $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise; +} + +export interface ExtHostChatProviderShape { + $provideChatResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; +} + +export interface MainThreadChatSlashCommandsShape extends IDisposable { + $registerCommand(handle: number, name: string): void; + $unregisterCommand(handle: number): void; + $handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise; +} + +export interface ExtHostChatSlashCommandsShape { + $executeCommand(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise; +} + export interface MainThreadInlineChatShape extends IDisposable { $registerInteractiveEditorProvider(handle: number, debugName: string, supportsFeedback: boolean): Promise; $handleProgressChunk(requestId: string, chunk: { message?: string; edits?: languages.TextEdit[] }): Promise; @@ -2517,6 +2539,8 @@ export interface MainThreadTestingShape { export const MainContext = { MainThreadAuthentication: createProxyIdentifier('MainThreadAuthentication'), MainThreadBulkEdits: createProxyIdentifier('MainThreadBulkEdits'), + MainThreadChatProvider: createProxyIdentifier('MainThreadChatProvider'), + MainThreadChatSlashCommands: createProxyIdentifier('MainThreadChatSlashCommands'), MainThreadClipboard: createProxyIdentifier('MainThreadClipboard'), MainThreadCommands: createProxyIdentifier('MainThreadCommands'), MainThreadComments: createProxyIdentifier('MainThreadComments'), @@ -2635,6 +2659,8 @@ export const ExtHostContext = { ExtHostInteractive: createProxyIdentifier('ExtHostInteractive'), ExtHostInlineChat: createProxyIdentifier('ExtHostInlineChatShape'), ExtHostChat: createProxyIdentifier('ExtHostChat'), + ExtHostChatSlashCommands: createProxyIdentifier('ExtHostChatSlashCommands'), + ExtHostChatProvider: createProxyIdentifier('ExtHostChatProvider'), ExtHostSemanticSimilarity: createProxyIdentifier('ExtHostSemanticSimilarity'), ExtHostTheming: createProxyIdentifier('ExtHostTheming'), ExtHostTunnelService: createProxyIdentifier('ExtHostTunnelService'), diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts new file mode 100644 index 00000000000..067cba16aca --- /dev/null +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ExtHostChatProviderShape, IMainContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; +import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; +import type * as vscode from 'vscode'; +import { Progress } from 'vs/platform/progress/common/progress'; +import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; + + +type ProviderData = { + readonly extension: ExtensionIdentifier; + readonly provider: vscode.ChatResponseProvider; +}; + + +export class ExtHostChatProvider implements ExtHostChatProviderShape { + + private static _idPool = 1; + + private readonly _proxy: MainThreadChatProviderShape; + private readonly _providers = new Map(); + + constructor( + mainContext: IMainContext, + private readonly _logService: ILogService, + ) { + this._proxy = mainContext.getProxy(MainContext.MainThreadChatProvider); + } + + all() { + return Array.from(this._providers.values(), v => v.provider); + } + + registerProvider(extension: ExtensionIdentifier, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { + + const handle = ExtHostChatProvider._idPool++; + this._providers.set(handle, { extension, provider }); + this._proxy.$registerProvider(handle, { extension, displayName: metadata.name ?? extension.value }); + + return toDisposable(() => { + this._proxy.$unregisterProvider(handle); + this._providers.delete(handle); + }); + } + + async $provideChatResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise { + const data = this._providers.get(handle); + if (!data) { + return; + } + const progress = new Progress(async fragment => { + if (token.isCancellationRequested) { + this._logService.warn(`[CHAT](${data.extension.value}) CANNOT send progress because the REQUEST IS CANCELLED`); + return; + } + await this._proxy.$handleProgressChunk(requestId, { index: fragment.index, part: fragment.part }); + }, { async: true }); + + return data.provider.provideChatResponse(messages.map(typeConvert.ChatMessage.to), options, progress, token); + } + +} diff --git a/src/vs/workbench/api/common/extHostChatSlashCommand.ts b/src/vs/workbench/api/common/extHostChatSlashCommand.ts new file mode 100644 index 00000000000..49f99bf8d70 --- /dev/null +++ b/src/vs/workbench/api/common/extHostChatSlashCommand.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ExtHostChatSlashCommandsShape, IMainContext, MainContext, MainThreadChatSlashCommandsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; +import { ChatMessageRole } from 'vs/workbench/api/common/extHostTypes'; +import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; +import type * as vscode from 'vscode'; +import { Progress } from 'vs/platform/progress/common/progress'; +import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; + +export class ExtHostChatSlashCommands implements ExtHostChatSlashCommandsShape { + + private static _idPool = 0; + + private readonly _commands = new Map(); + private readonly _proxy: MainThreadChatSlashCommandsShape; + + constructor( + mainContext: IMainContext, + private readonly _extHostChatProvider: ExtHostChatProvider, + private readonly _logService: ILogService, + ) { + this._proxy = mainContext.getProxy(MainContext.MainThreadChatSlashCommands); + } + + registerCommand(extension: ExtensionIdentifier, name: string, command: vscode.SlashCommand): IDisposable { + + const handle = ExtHostChatSlashCommands._idPool++; + this._commands.set(handle, { extension, command }); + this._proxy.$registerCommand(handle, name); + + return toDisposable(() => { + this._proxy.$unregisterCommand(handle); + this._commands.delete(handle); + }); + } + + async $executeCommand(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise { + const data = this._commands.get(handle); + if (!data) { + this._logService.warn(`[CHAT](${handle}) CANNOT execute command because the command is not registered`); + return; + } + + // TODO@jrieken this isn't proper, instead the code should call to the renderer which + // coordinates and picks the right provider + const provider = this._extHostChatProvider.all()[0]; + if (!provider) { + this._logService.warn(`[CHAT](${handle}) CANNOT execute command because there is no provider`); + return; + } + + await data.command( + provider, + { role: ChatMessageRole.User, content: prompt }, + { history: context.history.map(typeConvert.ChatMessage.to) }, + new Progress(p => { + this._proxy.$handleProgressChunk(requestId, { value: p.message.value }); + }), + token + ); + } +} diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index a4fa2e9340b..6eeecf18669 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -44,6 +44,7 @@ import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGro import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import type * as vscode from 'vscode'; import * as types from './extHostTypes'; +import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; export namespace Command { @@ -2155,3 +2156,23 @@ export namespace ChatFollowup { } } } + +export namespace ChatMessage { + export function to(message: chatProvider.IChatMessage): vscode.ChatMessage { + const res = new types.ChatMessage(ChatMessageRole.to(message.role), message.content); + res.name = message.name; + return res; + } +} + +export namespace ChatMessageRole { + + export function to(role: chatProvider.ChatMessageRole): vscode.ChatMessageRole { + switch (role) { + case chatProvider.ChatMessageRole.System: return types.ChatMessageRole.System; + case chatProvider.ChatMessageRole.User: return types.ChatMessageRole.User; + case chatProvider.ChatMessageRole.Assistant: return types.ChatMessageRole.Assistant; + case chatProvider.ChatMessageRole.Function: return types.ChatMessageRole.Function; + } + } +} diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 2647d41c5db..79eb3f08b26 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4095,4 +4095,23 @@ export enum InteractiveEditorResponseFeedbackKind { Undone = 2 } +export enum ChatMessageRole { + System = 0, + User = 1, + Assistant = 2, + Function = 3, +} + +export class ChatMessage implements vscode.ChatMessage { + + role: ChatMessageRole; + content: string; + name?: string; + + constructor(role: ChatMessageRole, content: string) { + this.role = role; + this.content = content; + } +} + //#endregion diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 656485ed70a..6fe1558dcf5 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -44,6 +44,8 @@ import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatCo import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { QuickQuestionMode } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction'; +import { ChatProviderService, IChatProviderService } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -201,4 +203,5 @@ registerSingleton(IChatContributionService, ChatContributionService, Instantiati registerSingleton(IChatWidgetService, ChatWidgetService, InstantiationType.Delayed); registerSingleton(IChatAccessibilityService, ChatAccessibilityService, InstantiationType.Delayed); registerSingleton(IChatWidgetHistoryService, ChatWidgetHistoryService, InstantiationType.Delayed); - +registerSingleton(IChatProviderService, ChatProviderService, InstantiationType.Delayed); +registerSingleton(IChatSlashCommandService, ChatSlashCommandService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/chatProvider.ts new file mode 100644 index 00000000000..8ed47816d7e --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/chatProvider.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IProgress } from 'vs/platform/progress/common/progress'; + +export const enum ChatMessageRole { + System, + User, + Assistant, + Function, +} + +export interface IChatMessage { + readonly role: ChatMessageRole; + readonly content: string; + readonly name?: string; +} + +export interface IChatResponseFragment { + index: number; + part: string; +} + +export interface IChatResponseProviderMetadata { + readonly extension: ExtensionIdentifier; + readonly displayName: string; + readonly description?: string; +} + +export interface IChatResponseProvider { + metadata: IChatResponseProviderMetadata; + provideChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Thenable; +} + +export const IChatProviderService = createDecorator('chatProviderService'); + +export interface IChatProviderService { + + readonly _serviceBrand: undefined; + + registerChatResponseProvider(provider: IChatResponseProvider): IDisposable; + getAllProviders(): Iterable; +} + +export class ChatProviderService implements IChatProviderService { + readonly _serviceBrand: undefined; + + private readonly _providers: Set = new Set(); + + registerChatResponseProvider(provider: IChatResponseProvider): IDisposable { + this._providers.add(provider); + return { + dispose: () => { + this._providers.delete(provider); + } + }; + } + + getAllProviders(): Iterable { + return this._providers; + } +} diff --git a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts new file mode 100644 index 00000000000..651cf22126d --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts @@ -0,0 +1,236 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Iterable } from 'vs/base/common/iterator'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IProgress, IProgressService, Progress, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { ChatMessageRole, IChatMessage, IChatProviderService, IChatResponseFragment } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IExtensionService, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; + +//#region extension point + +const slashItem: IJSONSchema = { + type: 'object', + required: ['name', 'detail'], + properties: { + name: { + type: 'string', + markdownDescription: localize('name', "The name of the slash command which will be used as prefix.") + }, + detail: { + type: 'string', + markdownDescription: localize('details', "The details of the slash command.") + }, + } +}; + +const slashItems: IJSONSchema = { + description: localize('vscode.extension.contributes.slashes', "Contributes slash commands to chat"), + oneOf: [ + slashItem, + { + type: 'array', + items: slashItem + } + ] +}; + +export const slashesExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'slashes', + jsonSchema: slashItems +}); + +//#region slash service, commands etc + +export interface IChatSlashData { + id: string; + name: string; + detail: string; +} + +export interface IChatSlashFragment { + value: string; +} + +export type IChatSlashCallback = { (prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise }; + +export const IChatSlashCommandService = createDecorator('chatSlashCommandService'); + +export interface IChatSlashCommandService { + _serviceBrand: undefined; + registerSlashData(data: IChatSlashData): IDisposable; + registerSlashCallback(id: string, command: IChatSlashCallback): IDisposable; + executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise; + getCommands(): Array; +} + +type Tuple = { data: IChatSlashData; command?: IChatSlashCallback }; + +export class ChatSlashCommandService implements IChatSlashCommandService { + + declare _serviceBrand: undefined; + + private readonly _commands = new Map(); + + constructor(@IExtensionService private readonly _extensionService: IExtensionService) { + + const contributions = new DisposableStore(); + + slashesExtPoint.setHandler(extensions => { + contributions.clear(); + + for (const entry of extensions) { + if (!isProposedApiEnabled(entry.description, 'chatSlashCommands')) { + entry.collector.error(`The ${slashesExtPoint.name} is proposed API`); + continue; + } + + const { value } = entry; + + for (const candidate of Iterable.wrap(value)) { + contributions.add(this.registerSlashData({ ...candidate, id: candidate.name })); + } + } + }); + } + + dispose(): void { + this._commands.clear(); + } + + registerSlashData(data: IChatSlashData): IDisposable { + if (this._commands.has(data.id)) { + throw new Error(`Already registered a command with id ${data.id}}`); + } + this._commands.set(data.id, { data }); + return toDisposable(() => this._commands.delete(data.id)); + } + + registerSlashCallback(id: string, command: IChatSlashCallback): IDisposable { + const data = this._commands.get(id); + if (!data) { + throw new Error(`No command with id ${id} registered`); + } + data.command = command; + return toDisposable(() => data.command = undefined); + } + + getCommands(): Array { + return Array.from(this._commands.values(), v => v.data); + } + + async executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise { + const data = this._commands.get(id); + if (!data) { + throw new Error('No command with id ${id} NOT registered'); + } + if (!data.command) { + await this._extensionService.activateByEvent(`onSlash:${id}`); + } + if (!data.command) { + throw new Error(`No command with id ${id} NOT resolved`); + } + + await data.command(prompt, progress, history, token); + } +} + + + + +// --- debug + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'chatbot.helloWorld', + title: 'Hello World', + category: 'Chatbot', + menu: { id: MenuId.CommandPalette } + }); + } + + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const chatProvider = accessor.get(IChatProviderService); + + for (const provider of chatProvider.getAllProviders()) { + + const p = new Progress(value => { + console.log(provider.metadata.displayName, value); + }); + + await provider.provideChatResponse([{ role: ChatMessageRole.User, content: 'Hello.' }], { n: 2 }, p, CancellationToken.None); + } + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'chatbot.helloWorld', + title: 'Slashes for the Masses', + category: 'Chatbot', + menu: { id: MenuId.CommandPalette } + }); + } + + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const quickPick = accessor.get(IQuickInputService); + const slashCommandService = accessor.get(IChatSlashCommandService); + const progress = accessor.get(IProgressService); + + const items: (IQuickPickItem & { cmd: IChatSlashData })[] = []; + for (const cmd of slashCommandService.getCommands()) { + items.push({ + cmd, + label: cmd.name, + detail: cmd.detail + }); + } + + const pick = await quickPick.pick(items, { placeHolder: 'Pick a command' }); + + if (!pick) { + return; + } + + const value = `/${pick.cmd.name} `; + const prompt = await quickPick.input({ value: value, valueSelection: [value.length, value.length], placeHolder: 'Enter a prompt' }); + + if (!prompt) { + return; + } + + const cts = new CancellationTokenSource(); + + progress.withProgress({ + location: ProgressLocation.Notification, + title: `${prompt}`, + cancellable: true, + }, async p => { + + const task = slashCommandService.executeCommand( + pick.cmd.id, + prompt, + new Progress(value => { + p.report({ message: value.value }); + console.log(value); + }), + [], + cts.token + ); + + await task; + }, () => cts.cancel()); + + } +}); diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 98f89dca708..448a524e212 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -9,6 +9,8 @@ export const allApiProposals = Object.freeze({ authGetSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authGetSessions.d.ts', authSession: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authSession.d.ts', canonicalUriProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts', + chatProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', + chatSlashCommands: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts', codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts', commentsDraftState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentsDraftState.d.ts', contribCommentEditorActionsMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentEditorActionsMenu.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts new file mode 100644 index 00000000000..e5edcafe90d --- /dev/null +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.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. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export enum ChatMessageRole { + System = 0, + User = 1, + Assistant = 2, + Function = 3, + } + + export class ChatMessage { + role: ChatMessageRole; + content: string; + name?: string; + + constructor(role: ChatMessageRole, content: string); + } + + // TODO: chat response builder + export interface ChatResponse { + message: ChatMessage; + } + + export interface ChatResponseFragment { + index: number; + part: string; + } + + export interface ChatResponseProvider { + provideChatResponse(messages: ChatMessage[], options: { [name: string]: any }, progress: Progress, token: CancellationToken): Thenable; + } + + export interface ChatResponseProviderMetadata { + // TODO: add way to compute token count + name: string; + } + + export namespace llm { + export function registerChatResponseProvider(provider: ChatResponseProvider, metadata: ChatResponseProviderMetadata): Disposable; + } + +} diff --git a/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts b/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts new file mode 100644 index 00000000000..487b458a5c7 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export interface SlashCommandContext { + + // messages so far + history: ChatMessage[]; + + // TODO: access to embeddings + // embeddings: {}; + + // TODO: access to "InputSourceId" + // DebugConsoleOutput + // Terminal + // CorrespondingTestFile + // CorrespondingImplementationFile + // ExtensionApi + // VSCode + // Workspace + } + + export interface SlashResponse { + message: MarkdownString; + // edits?: TextEdit[] | WorkspaceEdit; + } + + export interface SlashResult { + followUp?: InteractiveSessionFollowup[]; + } + + export interface SlashCommand { + + // TODO: Progress vs AsyncIterable + // CONSUMER of chat + (chat: ChatResponseProvider, prompt: ChatMessage, context: SlashCommandContext, progress: Progress, token: CancellationToken): Thenable; + } + + export namespace llm { + export function registerSlashCommand(name: string, command: SlashCommand): Disposable; + } +} From d7b089d5e3b5a56f131ace79b10b26ea0982da34 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 14 Jul 2023 20:38:25 +0200 Subject: [PATCH 02/69] proper request making, ensure fetcher cannot be used after command is done --- .../api/browser/mainThreadChatProvider.ts | 11 +++++-- .../workbench/api/common/extHost.protocol.ts | 3 ++ .../api/common/extHostChatProvider.ts | 25 +++++++++++----- .../api/common/extHostChatSlashCommand.ts | 29 ++++++++++++++----- .../api/common/extHostTypeConverters.ts | 21 ++++++++++++++ .../contrib/chat/common/chatProvider.ts | 14 ++++++--- .../contrib/chat/common/chatSlashCommands.ts | 13 +++------ 7 files changed, 87 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index bdb067ec1d8..082c0b417d9 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableMap } from 'vs/base/common/lifecycle'; -import { IProgress } from 'vs/platform/progress/common/progress'; +import { IProgress, Progress } from 'vs/platform/progress/common/progress'; import { ExtHostChatProviderShape, ExtHostContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; -import { IChatResponseProviderMetadata, IChatResponseFragment, IChatProviderService } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatResponseProviderMetadata, IChatResponseFragment, IChatProviderService, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadChatProvider) @@ -50,4 +51,10 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { $unregisterProvider(handle: number): void { this._providerRegistrations.deleteAndDispose(handle); } + + async $fetchResponse(requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { + return this._chatProviderService.fetchChatResponse(messages, options, new Progress(value => { + this._proxy.$handleResponseFragment(requestId, value); + }), token); + } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index cf713f31a88..a209eb6a7e4 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1125,10 +1125,13 @@ export interface MainThreadChatProviderShape extends IDisposable { $registerProvider(handle: number, metadata: IChatResponseProviderMetadata): void; $unregisterProvider(handle: number): void; $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise; + + $fetchResponse(requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise; } export interface ExtHostChatProviderShape { $provideChatResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; + $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise; } export interface MainThreadChatSlashCommandsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 067cba16aca..99b55b9cb32 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -10,16 +10,14 @@ import { ExtHostChatProviderShape, IMainContext, MainContext, MainThreadChatProv import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; -import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatMessage, IChatResponseFragment } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; - type ProviderData = { readonly extension: ExtensionIdentifier; readonly provider: vscode.ChatResponseProvider; }; - export class ExtHostChatProvider implements ExtHostChatProviderShape { private static _idPool = 1; @@ -34,10 +32,6 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._proxy = mainContext.getProxy(MainContext.MainThreadChatProvider); } - all() { - return Array.from(this._providers.values(), v => v.provider); - } - registerProvider(extension: ExtensionIdentifier, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { const handle = ExtHostChatProvider._idPool++; @@ -66,4 +60,21 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return data.provider.provideChatResponse(messages.map(typeConvert.ChatMessage.to), options, progress, token); } + //#region --- making request + + private readonly _pendingRequest = new Map>(); + + async makeChatRequest(messages: vscode.ChatMessage[], options: { [name: string]: any }, progress: vscode.Progress, token: CancellationToken) { + const requestId = (Math.random() * 1e6) | 0; + this._pendingRequest.set(requestId, progress); + try { + await this._proxy.$fetchResponse(requestId, messages.map(typeConvert.ChatMessage.from), options, token); + } finally { + this._pendingRequest.delete(requestId); + } + } + + async $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise { + this._pendingRequest.get(requestId)?.report(chunk); + } } diff --git a/src/vs/workbench/api/common/extHostChatSlashCommand.ts b/src/vs/workbench/api/common/extHostChatSlashCommand.ts index 49f99bf8d70..1a0375199c4 100644 --- a/src/vs/workbench/api/common/extHostChatSlashCommand.ts +++ b/src/vs/workbench/api/common/extHostChatSlashCommand.ts @@ -14,6 +14,7 @@ import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { raceCancellation } from 'vs/base/common/async'; export class ExtHostChatSlashCommands implements ExtHostChatSlashCommandsShape { @@ -49,22 +50,36 @@ export class ExtHostChatSlashCommands implements ExtHostChatSlashCommandsShape { return; } - // TODO@jrieken this isn't proper, instead the code should call to the renderer which - // coordinates and picks the right provider - const provider = this._extHostChatProvider.all()[0]; - if (!provider) { - this._logService.warn(`[CHAT](${handle}) CANNOT execute command because there is no provider`); - return; + let done = false; + const that = this; + function throwIfDone() { + if (done) { + throw new Error('Only valid while executing the command'); + } } - await data.command( + const provider: vscode.ChatResponseProvider = { + provideChatResponse(messages, options, progress, token) { + throwIfDone(); + return that._extHostChatProvider.makeChatRequest(messages, options, progress, token); + } + }; + + const task = data.command( provider, { role: ChatMessageRole.User, content: prompt }, { history: context.history.map(typeConvert.ChatMessage.to) }, new Progress(p => { + throwIfDone(); this._proxy.$handleProgressChunk(requestId, { value: p.message.value }); }), token ); + + try { + await raceCancellation(Promise.resolve(task), token); + } finally { + done = true; + } } } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 6eeecf18669..a0390023241 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2163,8 +2163,18 @@ export namespace ChatMessage { res.name = message.name; return res; } + + + export function from(message: vscode.ChatMessage): chatProvider.IChatMessage { + return { + role: ChatMessageRole.from(message.role), + content: message.content, + name: message.name + }; + } } + export namespace ChatMessageRole { export function to(role: chatProvider.ChatMessageRole): vscode.ChatMessageRole { @@ -2175,4 +2185,15 @@ export namespace ChatMessageRole { case chatProvider.ChatMessageRole.Function: return types.ChatMessageRole.Function; } } + + export function from(role: vscode.ChatMessageRole): chatProvider.ChatMessageRole { + switch (role) { + case types.ChatMessageRole.System: return chatProvider.ChatMessageRole.System; + case types.ChatMessageRole.Assistant: return chatProvider.ChatMessageRole.Assistant; + case types.ChatMessageRole.Function: return chatProvider.ChatMessageRole.Function; + case types.ChatMessageRole.User: + default: + return chatProvider.ChatMessageRole.User; + } + } } diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/chatProvider.ts index 8ed47816d7e..a905a6e267a 100644 --- a/src/vs/workbench/contrib/chat/common/chatProvider.ts +++ b/src/vs/workbench/contrib/chat/common/chatProvider.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; +import { Iterable } from 'vs/base/common/iterator'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -35,7 +36,7 @@ export interface IChatResponseProviderMetadata { export interface IChatResponseProvider { metadata: IChatResponseProviderMetadata; - provideChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Thenable; + provideChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; } export const IChatProviderService = createDecorator('chatProviderService'); @@ -45,7 +46,8 @@ export interface IChatProviderService { readonly _serviceBrand: undefined; registerChatResponseProvider(provider: IChatResponseProvider): IDisposable; - getAllProviders(): Iterable; + + fetchChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; } export class ChatProviderService implements IChatProviderService { @@ -62,7 +64,11 @@ export class ChatProviderService implements IChatProviderService { }; } - getAllProviders(): Iterable { - return this._providers; + fetchChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise { + const provider = Iterable.first(this._providers); // TODO@jrieken have plan how N providers are handled + if (!provider) { + throw new Error('NO chat provider registered'); + } + return provider.provideChatResponse(messages, options, progress, token); } } diff --git a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts index 651cf22126d..1b4cbcc31e6 100644 --- a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts +++ b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts @@ -145,8 +145,6 @@ export class ChatSlashCommandService implements IChatSlashCommandService { } - - // --- debug registerAction2(class extends Action2 { @@ -162,14 +160,11 @@ registerAction2(class extends Action2 { override async run(accessor: ServicesAccessor, ...args: any[]): Promise { const chatProvider = accessor.get(IChatProviderService); - for (const provider of chatProvider.getAllProviders()) { + const p = new Progress(value => { + console.log(value); + }); + await chatProvider.fetchChatResponse([{ role: ChatMessageRole.User, content: 'Hello.' }], { n: 2 }, p, CancellationToken.None); - const p = new Progress(value => { - console.log(provider.metadata.displayName, value); - }); - - await provider.provideChatResponse([{ role: ChatMessageRole.User, content: 'Hello.' }], { n: 2 }, p, CancellationToken.None); - } } }); From 7c00c24205df7efb78361268c09297c3b0b50098 Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 17 Jul 2023 14:30:08 +0200 Subject: [PATCH 03/69] hook up rich slash commands with UI --- .../api/common/extHostChatSlashCommand.ts | 2 +- .../contrib/chat/common/chatServiceImpl.ts | 49 ++++++++++++++++--- .../contrib/chat/common/chatSlashCommands.ts | 9 +++- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatSlashCommand.ts b/src/vs/workbench/api/common/extHostChatSlashCommand.ts index 1a0375199c4..9d2161171af 100644 --- a/src/vs/workbench/api/common/extHostChatSlashCommand.ts +++ b/src/vs/workbench/api/common/extHostChatSlashCommand.ts @@ -71,7 +71,7 @@ export class ExtHostChatSlashCommands implements ExtHostChatSlashCommandsShape { { history: context.history.map(typeConvert.ChatMessage.to) }, new Progress(p => { throwIfDone(); - this._proxy.$handleProgressChunk(requestId, { value: p.message.value }); + this._proxy.$handleProgressChunk(requestId, { content: p.message.value }); }), token ); diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 68a14aaf8b7..14cd0ef9003 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -17,12 +17,15 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; +import { Progress } from 'vs/platform/progress/common/progress'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatModel, ChatWelcomeMessageModel, IChatModel, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatProgress, IChatProvider, IChatProviderInfo, IChatReplyFollowup, IChatService, IChatUserActionEvent, ISlashCommand, ISlashCommandProvider, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatProgress, IChatProvider, IChatProviderInfo, IChatReplyFollowup, IChatResponse, IChatService, IChatUserActionEvent, ISlashCommand, ISlashCommandProvider, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatSlashCommandService, IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; const serializedChatKey = 'interactive.sessions'; @@ -145,7 +148,8 @@ export class ChatService extends Disposable implements IChatService { @IInstantiationService private readonly instantiationService: IInstantiationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService, ) { super(); @@ -449,7 +453,27 @@ export class ChatService extends Disposable implements IChatService { if (usedSlashCommand?.command) { this._onDidSubmitSlashCommand.fire({ slashCommand: usedSlashCommand.command, sessionId: model.sessionId }); } - let rawResponse = await provider.provideReply({ session: model.session!, message: resolvedCommand }, progressCallback, token); + + let rawResponse: IChatResponse | null | undefined; + + if ((typeof resolvedCommand === 'string' && typeof message === 'string' && this.chatSlashCommandService.hasCommand(resolvedCommand))) { + // contributed slash commands + // TODO: spell this out in the UI + const history: IChatMessage[] = []; + for (const request of model.getRequests()) { + if (typeof request.message !== 'string' || !request.response) { + continue; + } + history.push({ role: ChatMessageRole.User, content: request.message }); + history.push({ role: ChatMessageRole.Assistant, content: request.response?.response.value }); + } + await this.chatSlashCommandService.executeCommand(resolvedCommand, message.substring(resolvedCommand.length + 1).trimStart(), new Progress(p => progressCallback(p)), history, token); + rawResponse = { session: model.session! }; + + } else { + rawResponse = await provider.provideReply({ session: model.session!, message: resolvedCommand }, progressCallback, token); + } + if (token.isCancellationRequested) { return; } else { @@ -510,11 +534,15 @@ export class ChatService extends Disposable implements IChatService { private async handleSlashCommand(sessionId: string, command: string): Promise { const slashCommands = await this.getSlashCommands(sessionId, CancellationToken.None); for (const slashCommand of slashCommands ?? []) { - if (command.startsWith(`/${slashCommand.command}`) && slashCommand.provider) { - return await slashCommand.provider.resolveSlashCommand(command, CancellationToken.None) ?? command; + if (command.startsWith(`/${slashCommand.command}`)) { + if (slashCommand.provider) { + return await slashCommand.provider.resolveSlashCommand(command, CancellationToken.None) ?? command; + } else if (this.chatSlashCommandService.hasCommand(slashCommand.command)) { + return slashCommand.command; + } + } } - return command; } @@ -542,9 +570,16 @@ export class ChatService extends Disposable implements IChatService { .then(commands => commands?.map(c => ({ ...c, provider: p })))) ]); + const serviceResults = this.chatSlashCommandService.getCommands().map(data => { + return { + command: data.name, + detail: data.detail, + }; + }); + try { const slashCommands = (await providerResults).filter(c => !!c) as ISlashCommand[][]; - return withNullAsUndefined(slashCommands.flat()); + return slashCommands.flat().concat(serviceResults); } catch (e) { this.logService.error(e); diff --git a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts index 1b4cbcc31e6..587c62e6f8a 100644 --- a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts +++ b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts @@ -58,7 +58,7 @@ export interface IChatSlashData { } export interface IChatSlashFragment { - value: string; + content: string; } export type IChatSlashCallback = { (prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise }; @@ -71,6 +71,7 @@ export interface IChatSlashCommandService { registerSlashCallback(id: string, command: IChatSlashCallback): IDisposable; executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise; getCommands(): Array; + hasCommand(id: string): boolean; } type Tuple = { data: IChatSlashData; command?: IChatSlashCallback }; @@ -128,6 +129,10 @@ export class ChatSlashCommandService implements IChatSlashCommandService { return Array.from(this._commands.values(), v => v.data); } + hasCommand(id: string): boolean { + return this._commands.has(id); + } + async executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise { const data = this._commands.get(id); if (!data) { @@ -217,7 +222,7 @@ registerAction2(class extends Action2 { pick.cmd.id, prompt, new Progress(value => { - p.report({ message: value.value }); + p.report({ message: value.content }); console.log(value); }), [], From a155dbb793fdab55451a2793b13cd10a9b81cc2c Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 18 Jul 2023 15:39:00 +0200 Subject: [PATCH 04/69] register chat provider with an identifier --- .../api/browser/mainThreadChatProvider.ts | 4 ++-- .../workbench/api/common/extHost.api.impl.ts | 4 ++-- .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostChatProvider.ts | 4 ++-- .../contrib/chat/common/chatProvider.ts | 22 +++++++++---------- .../vscode.proposed.chatProvider.d.ts | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 082c0b417d9..790f94d34b2 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -28,8 +28,8 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { this._providerRegistrations.dispose(); } - $registerProvider(handle: number, metadata: IChatResponseProviderMetadata): void { - const registration = this._chatProviderService.registerChatResponseProvider({ + $registerProvider(handle: number, identifier: string, metadata: IChatResponseProviderMetadata): void { + const registration = this._chatProviderService.registerChatResponseProvider(identifier, { metadata, provideChatResponse: async (messages, options, progress, token) => { const requestId = (Math.random() * 1e6) | 0; diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 14a51200438..1df830af4ce 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1330,9 +1330,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: llm const llm: typeof vscode.llm = { - registerChatResponseProvider(provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata) { + registerChatResponseProvider(id: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata) { checkProposedApiEnabled(extension, 'chatProvider'); - return extHostChatProvider.registerProvider(extension.identifier, provider, metadata); + return extHostChatProvider.registerProvider(extension.identifier, id, provider, metadata); }, registerSlashCommand(name: string, command: vscode.SlashCommand) { checkProposedApiEnabled(extension, 'chatSlashCommands'); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b0209907460..546b82ab054 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1127,7 +1127,7 @@ export interface MainThreadInteractiveShape extends IDisposable { } export interface MainThreadChatProviderShape extends IDisposable { - $registerProvider(handle: number, metadata: IChatResponseProviderMetadata): void; + $registerProvider(handle: number, identifier: string, metadata: IChatResponseProviderMetadata): void; $unregisterProvider(handle: number): void; $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise; diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 99b55b9cb32..a03601337e2 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -32,11 +32,11 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._proxy = mainContext.getProxy(MainContext.MainThreadChatProvider); } - registerProvider(extension: ExtensionIdentifier, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { + registerProvider(extension: ExtensionIdentifier, identifier: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { const handle = ExtHostChatProvider._idPool++; this._providers.set(handle, { extension, provider }); - this._proxy.$registerProvider(handle, { extension, displayName: metadata.name ?? extension.value }); + this._proxy.$registerProvider(handle, identifier, { extension, displayName: metadata.name ?? extension.value }); return toDisposable(() => { this._proxy.$unregisterProvider(handle); diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/chatProvider.ts index a905a6e267a..5dc0e6b113c 100644 --- a/src/vs/workbench/contrib/chat/common/chatProvider.ts +++ b/src/vs/workbench/contrib/chat/common/chatProvider.ts @@ -5,7 +5,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Iterable } from 'vs/base/common/iterator'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProgress } from 'vs/platform/progress/common/progress'; @@ -45,7 +45,7 @@ export interface IChatProviderService { readonly _serviceBrand: undefined; - registerChatResponseProvider(provider: IChatResponseProvider): IDisposable; + registerChatResponseProvider(identifier: string, provider: IChatResponseProvider): IDisposable; fetchChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; } @@ -53,19 +53,19 @@ export interface IChatProviderService { export class ChatProviderService implements IChatProviderService { readonly _serviceBrand: undefined; - private readonly _providers: Set = new Set(); + private readonly _providers: Map = new Map(); - registerChatResponseProvider(provider: IChatResponseProvider): IDisposable { - this._providers.add(provider); - return { - dispose: () => { - this._providers.delete(provider); - } - }; + + registerChatResponseProvider(identifier: string, provider: IChatResponseProvider): IDisposable { + if (this._providers.has(identifier)) { + throw new Error(`Chat response provider with identifier ${identifier} is already registered.`); + } + this._providers.set(identifier, provider); + return toDisposable(() => this._providers.delete(identifier)); } fetchChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise { - const provider = Iterable.first(this._providers); // TODO@jrieken have plan how N providers are handled + const provider = Iterable.first(this._providers.values()); // TODO@jrieken have plan how N providers are handled if (!provider) { throw new Error('NO chat provider registered'); } diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index e5edcafe90d..5cb5b166a2b 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -40,7 +40,7 @@ declare module 'vscode' { } export namespace llm { - export function registerChatResponseProvider(provider: ChatResponseProvider, metadata: ChatResponseProviderMetadata): Disposable; + export function registerChatResponseProvider(id: string, provider: ChatResponseProvider, metadata: ChatResponseProviderMetadata): Disposable; } } From b085936579b3df8410c4e0e71c6083b5b4a0c78b Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 18 Jul 2023 15:39:34 +0200 Subject: [PATCH 05/69] remove debug/test commands --- .../contrib/chat/common/chatSlashCommands.ts | 96 +------------------ 1 file changed, 4 insertions(+), 92 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts index 587c62e6f8a..576f5a502c8 100644 --- a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts +++ b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts @@ -3,16 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { Iterable } from 'vs/base/common/iterator'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; -import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IProgress, IProgressService, Progress, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { ChatMessageRole, IChatMessage, IChatProviderService, IChatResponseFragment } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IProgress } from 'vs/platform/progress/common/progress'; +import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { IExtensionService, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -148,89 +146,3 @@ export class ChatSlashCommandService implements IChatSlashCommandService { await data.command(prompt, progress, history, token); } } - - -// --- debug - -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'chatbot.helloWorld', - title: 'Hello World', - category: 'Chatbot', - menu: { id: MenuId.CommandPalette } - }); - } - - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { - const chatProvider = accessor.get(IChatProviderService); - - const p = new Progress(value => { - console.log(value); - }); - await chatProvider.fetchChatResponse([{ role: ChatMessageRole.User, content: 'Hello.' }], { n: 2 }, p, CancellationToken.None); - - } -}); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'chatbot.helloWorld', - title: 'Slashes for the Masses', - category: 'Chatbot', - menu: { id: MenuId.CommandPalette } - }); - } - - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { - const quickPick = accessor.get(IQuickInputService); - const slashCommandService = accessor.get(IChatSlashCommandService); - const progress = accessor.get(IProgressService); - - const items: (IQuickPickItem & { cmd: IChatSlashData })[] = []; - for (const cmd of slashCommandService.getCommands()) { - items.push({ - cmd, - label: cmd.name, - detail: cmd.detail - }); - } - - const pick = await quickPick.pick(items, { placeHolder: 'Pick a command' }); - - if (!pick) { - return; - } - - const value = `/${pick.cmd.name} `; - const prompt = await quickPick.input({ value: value, valueSelection: [value.length, value.length], placeHolder: 'Enter a prompt' }); - - if (!prompt) { - return; - } - - const cts = new CancellationTokenSource(); - - progress.withProgress({ - location: ProgressLocation.Notification, - title: `${prompt}`, - cancellable: true, - }, async p => { - - const task = slashCommandService.executeCommand( - pick.cmd.id, - prompt, - new Progress(value => { - p.report({ message: value.content }); - console.log(value); - }), - [], - cts.token - ); - - await task; - }, () => cts.cancel()); - - } -}); From 3cfa5b9ad4a69042a84b0011f4dc0c9493d3caf0 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 19 Jul 2023 15:04:44 +0200 Subject: [PATCH 06/69] add `requestChatAccess` proposal --- .../api/browser/mainThreadChatProvider.ts | 17 +++++++- .../workbench/api/common/extHost.api.impl.ts | 4 ++ .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostChatProvider.ts | 43 +++++++++++++++---- .../api/common/extHostChatSlashCommand.ts | 15 +++---- .../contrib/chat/common/chatProvider.ts | 9 ++-- .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.chatRequestAccess.d.ts | 38 ++++++++++++++++ .../vscode.proposed.chatSlashCommands.d.ts | 4 +- 9 files changed, 105 insertions(+), 28 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 790f94d34b2..22624a775dd 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -5,6 +5,8 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableMap } from 'vs/base/common/lifecycle'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; import { IProgress, Progress } from 'vs/platform/progress/common/progress'; import { ExtHostChatProviderShape, ExtHostContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; import { IChatResponseProviderMetadata, IChatResponseFragment, IChatProviderService, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; @@ -20,6 +22,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { constructor( extHostContext: IExtHostContext, @IChatProviderService private readonly _chatProviderService: IChatProviderService, + @ILogService private readonly _logService: ILogService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider); } @@ -52,9 +55,19 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { this._providerRegistrations.deleteAndDispose(handle); } - async $fetchResponse(requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { - return this._chatProviderService.fetchChatResponse(messages, options, new Progress(value => { + async $fetchResponse(extension: ExtensionIdentifier, providerId: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { + this._logService.debug('[CHAT] extension request STARTED', extension.value, requestId); + + const task = this._chatProviderService.fetchChatResponse(providerId, messages, options, new Progress(value => { this._proxy.$handleResponseFragment(requestId, value); }), token); + + task.catch(err => { + this._logService.error('[CHAT] extension request ERRORED', err, extension.value, requestId); + }).finally(() => { + this._logService.debug('[CHAT] extension request DONE', extension.value, requestId); + }); + + return task; } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 1df830af4ce..3dadc71e905 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1337,6 +1337,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerSlashCommand(name: string, command: vscode.SlashCommand) { checkProposedApiEnabled(extension, 'chatSlashCommands'); return extHostChatSlashCommands.registerCommand(extension.identifier, name, command); + }, + requestChatAccess(id: string) { + checkProposedApiEnabled(extension, 'chatRequestAccess'); + return extHostChatProvider.requestChatResponseProvider(extension.identifier, id); } }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 546b82ab054..ae479394a00 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1131,7 +1131,7 @@ export interface MainThreadChatProviderShape extends IDisposable { $unregisterProvider(handle: number): void; $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise; - $fetchResponse(requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise; + $fetchResponse(extension: ExtensionIdentifier, provider: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise; } export interface ExtHostChatProviderShape { diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index a03601337e2..e7679f4bfd4 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -11,7 +11,7 @@ import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; import { IChatMessage, IChatResponseFragment } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionIdentifierMap } from 'vs/platform/extensions/common/extensions'; type ProviderData = { readonly extension: ExtensionIdentifier; @@ -64,14 +64,41 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { private readonly _pendingRequest = new Map>(); - async makeChatRequest(messages: vscode.ChatMessage[], options: { [name: string]: any }, progress: vscode.Progress, token: CancellationToken) { - const requestId = (Math.random() * 1e6) | 0; - this._pendingRequest.set(requestId, progress); - try { - await this._proxy.$fetchResponse(requestId, messages.map(typeConvert.ChatMessage.from), options, token); - } finally { - this._pendingRequest.delete(requestId); + private readonly _chatAccessAllowList = new ExtensionIdentifierMap>(); + + allowListExtensionWhile(extension: ExtensionIdentifier, promise: Promise): void { + this._chatAccessAllowList.set(extension, promise); + promise.finally(() => this._chatAccessAllowList.delete(extension)); + } + + async requestChatResponseProvider(from: ExtensionIdentifier, identifier: string): Promise { + // check if a UI command is running/active + + if (!this._chatAccessAllowList.has(from)) { + throw new Error('Extension is NOT allowed to make chat requests'); } + + const that = this; + + return { + get isRevoked() { + return !that._chatAccessAllowList.has(from); + }, + async makeRequest(messages, options, progress, token) { + + if (!that._chatAccessAllowList.has(from)) { + throw new Error('Access to chat has been revoked'); + } + + const requestId = (Math.random() * 1e6) | 0; + that._pendingRequest.set(requestId, progress); + try { + await that._proxy.$fetchResponse(from, identifier, requestId, messages.map(typeConvert.ChatMessage.from), options, token); + } finally { + that._pendingRequest.delete(requestId); + } + }, + }; } async $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise { diff --git a/src/vs/workbench/api/common/extHostChatSlashCommand.ts b/src/vs/workbench/api/common/extHostChatSlashCommand.ts index 9d2161171af..6fbfeab805a 100644 --- a/src/vs/workbench/api/common/extHostChatSlashCommand.ts +++ b/src/vs/workbench/api/common/extHostChatSlashCommand.ts @@ -14,7 +14,7 @@ import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { raceCancellation } from 'vs/base/common/async'; +import { DeferredPromise, raceCancellation } from 'vs/base/common/async'; export class ExtHostChatSlashCommands implements ExtHostChatSlashCommandsShape { @@ -51,22 +51,18 @@ export class ExtHostChatSlashCommands implements ExtHostChatSlashCommandsShape { } let done = false; - const that = this; function throwIfDone() { if (done) { throw new Error('Only valid while executing the command'); } } - const provider: vscode.ChatResponseProvider = { - provideChatResponse(messages, options, progress, token) { - throwIfDone(); - return that._extHostChatProvider.makeChatRequest(messages, options, progress, token); - } - }; + const commandExecution = new DeferredPromise(); + token.onCancellationRequested(() => commandExecution.complete()); + setTimeout(() => commandExecution.complete(), 3 * 1000); + this._extHostChatProvider.allowListExtensionWhile(data.extension, commandExecution.p); const task = data.command( - provider, { role: ChatMessageRole.User, content: prompt }, { history: context.history.map(typeConvert.ChatMessage.to) }, new Progress(p => { @@ -80,6 +76,7 @@ export class ExtHostChatSlashCommands implements ExtHostChatSlashCommandsShape { await raceCancellation(Promise.resolve(task), token); } finally { done = true; + commandExecution.complete(); } } } diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/chatProvider.ts index 5dc0e6b113c..8bd56ef0465 100644 --- a/src/vs/workbench/contrib/chat/common/chatProvider.ts +++ b/src/vs/workbench/contrib/chat/common/chatProvider.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; -import { Iterable } from 'vs/base/common/iterator'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -47,7 +46,7 @@ export interface IChatProviderService { registerChatResponseProvider(identifier: string, provider: IChatResponseProvider): IDisposable; - fetchChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; + fetchChatResponse(identifier: string, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; } export class ChatProviderService implements IChatProviderService { @@ -64,10 +63,10 @@ export class ChatProviderService implements IChatProviderService { return toDisposable(() => this._providers.delete(identifier)); } - fetchChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise { - const provider = Iterable.first(this._providers.values()); // TODO@jrieken have plan how N providers are handled + fetchChatResponse(identifier: string, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise { + const provider = this._providers.get(identifier); if (!provider) { - throw new Error('NO chat provider registered'); + throw new Error(`Chat response provider with identifier ${identifier} is not registered.`); } return provider.provideChatResponse(messages, options, progress, token); } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 70eb90fadc4..90cc09aaf35 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -10,6 +10,7 @@ export const allApiProposals = Object.freeze({ authSession: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authSession.d.ts', canonicalUriProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts', chatProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', + chatRequestAccess: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts', chatSlashCommands: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts', codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts', commentsDraftState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentsDraftState.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts new file mode 100644 index 00000000000..de88c0fe6ce --- /dev/null +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export interface ChatAccess { + + /** + * Whether the access to chat has been revoked. This happens when the user interaction that allowed for + * chat access is finished. + */ + isRevoked: boolean; + + /** + * TODO: return an AsyncIterable instead of asking to pass Progress<...>? + * + * @param messages + * @param options + * @param progress + * @param token + */ + makeRequest(messages: ChatMessage[], options: { [name: string]: any }, progress: Progress, token: CancellationToken): Thenable; + } + + export namespace llm { + + /** + * Request access to chat. + * + * *Note* that this function will throw an error unless an user interaction is currently active. + * + * @param id The id of the chat provider, e.g `chatgpt` + */ + export function requestChatAccess(id: string): Thenable; + } +} diff --git a/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts b/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts index 487b458a5c7..53119d85a17 100644 --- a/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts +++ b/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts @@ -34,9 +34,7 @@ declare module 'vscode' { export interface SlashCommand { - // TODO: Progress vs AsyncIterable - // CONSUMER of chat - (chat: ChatResponseProvider, prompt: ChatMessage, context: SlashCommandContext, progress: Progress, token: CancellationToken): Thenable; + (prompt: ChatMessage, context: SlashCommandContext, progress: Progress, token: CancellationToken): Thenable; } export namespace llm { From 238c332119888f00a3564a0ebb75cb788f943dfd Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 19 Jul 2023 18:33:12 +0200 Subject: [PATCH 07/69] some comments for concepts --- .../vscode.proposed.chatProvider.d.ts | 19 ++++++++++++++----- .../vscode.proposed.chatRequestAccess.d.ts | 6 +++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index 5cb5b166a2b..e4d56810d76 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -5,6 +5,7 @@ declare module 'vscode' { + // ChatML export enum ChatMessageRole { System = 0, User = 1, @@ -12,6 +13,7 @@ declare module 'vscode' { Function = 3, } + // ChatML export class ChatMessage { role: ChatMessageRole; content: string; @@ -20,16 +22,14 @@ declare module 'vscode' { constructor(role: ChatMessageRole, content: string); } - // TODO: chat response builder - export interface ChatResponse { - message: ChatMessage; - } - export interface ChatResponseFragment { index: number; part: string; } + /** + * Represents a large language model that accepts ChatML messages and produces a streaming response + */ export interface ChatResponseProvider { provideChatResponse(messages: ChatMessage[], options: { [name: string]: any }, progress: Progress, token: CancellationToken): Thenable; } @@ -40,6 +40,15 @@ declare module 'vscode' { } export namespace llm { + + /** + * Register a LLM as chat response provider to the editor. + * + * + * @param id + * @param provider + * @param metadata + */ export function registerChatResponseProvider(id: string, provider: ChatResponseProvider, metadata: ChatResponseProviderMetadata): Disposable; } diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts index de88c0fe6ce..45e15accb2a 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -5,6 +5,10 @@ declare module 'vscode' { + /** + * Represents access to using a chat provider (LLM). Access is granted and temporary, usually only valid + * for the duration of an user interaction. + */ export interface ChatAccess { /** @@ -31,7 +35,7 @@ declare module 'vscode' { * * *Note* that this function will throw an error unless an user interaction is currently active. * - * @param id The id of the chat provider, e.g `chatgpt` + * @param id The id of the chat provider, e.g `copilot` */ export function requestChatAccess(id: string): Thenable; } From 2da44e17588f834d77c40fb2b0696e083fef763b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 5 Aug 2023 08:51:23 -0700 Subject: [PATCH 08/69] Improve management of events/memory in terminal Part of #187082 --- .../capabilities/bufferMarkCapability.ts | 6 +- .../commandDetectionCapability.ts | 20 +-- .../capabilities/cwdDetectionCapability.ts | 5 +- .../common/xterm/shellIntegrationAddon.ts | 6 +- .../terminal/browser/terminalConfigHelper.ts | 6 +- .../terminal/browser/terminalEditorService.ts | 10 +- .../contrib/terminal/browser/terminalGroup.ts | 18 +-- .../terminal/browser/terminalGroupService.ts | 20 +-- .../terminal/browser/terminalInstance.ts | 116 +++++++++--------- .../browser/terminalInstanceService.ts | 2 +- .../browser/terminalProfileService.ts | 8 +- .../terminal/browser/terminalService.ts | 50 ++++---- .../terminal/browser/terminalTabsList.ts | 23 ++-- .../terminal/browser/xterm/suggestAddon.ts | 4 +- .../terminal/browser/xterm/xtermTerminal.ts | 16 +-- .../common/environmentVariableService.ts | 7 +- 16 files changed, 165 insertions(+), 152 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/bufferMarkCapability.ts b/src/vs/platform/terminal/common/capabilities/bufferMarkCapability.ts index 229702af9d6..9759eeb3c59 100644 --- a/src/vs/platform/terminal/common/capabilities/bufferMarkCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/bufferMarkCapability.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IBufferMarkCapability, TerminalCapability, IMarkProperties } from 'vs/platform/terminal/common/capabilities/capabilities'; // Importing types is safe in any layer // eslint-disable-next-line local/code-import-patterns @@ -13,19 +14,20 @@ import type { IMarker, Terminal } from 'xterm-headless'; * Manages "marks" in the buffer which are lines that are tracked when lines are added to or removed * from the buffer. */ -export class BufferMarkCapability implements IBufferMarkCapability { +export class BufferMarkCapability extends Disposable implements IBufferMarkCapability { readonly type = TerminalCapability.BufferMarkDetection; private _idToMarkerMap: Map = new Map(); private _anonymousMarkers: Map = new Map(); - private readonly _onMarkAdded = new Emitter(); + private readonly _onMarkAdded = this._register(new Emitter()); readonly onMarkAdded = this._onMarkAdded.event; constructor( private readonly _terminal: Terminal ) { + super(); } *markers(): IterableIterator { diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 2fec93e7b7b..c9005bff55f 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -6,6 +6,7 @@ import { timeout } from 'vs/base/common/async'; import { debounce } from 'vs/base/common/decorators'; import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { ICommandDetectionCapability, TerminalCapability, ITerminalCommand, IHandleCommandOptions, ICommandInvalidationRequest, CommandInvalidationReason, ISerializedTerminalCommand, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { ITerminalOutputMatch, ITerminalOutputMatcher } from 'vs/platform/terminal/common/terminal'; @@ -55,7 +56,7 @@ interface ITerminalDimensions { rows: number; } -export class CommandDetectionCapability implements ICommandDetectionCapability { +export class CommandDetectionCapability extends Disposable implements ICommandDetectionCapability { readonly type = TerminalCapability.CommandDetection; protected _commands: ITerminalCommand[] = []; @@ -97,29 +98,30 @@ export class CommandDetectionCapability implements ICommandDetectionCapability { return true; } - private readonly _onCommandStarted = new Emitter(); + private readonly _onCommandStarted = this._register(new Emitter()); readonly onCommandStarted = this._onCommandStarted.event; - private readonly _onBeforeCommandFinished = new Emitter(); + private readonly _onBeforeCommandFinished = this._register(new Emitter()); readonly onBeforeCommandFinished = this._onBeforeCommandFinished.event; - private readonly _onCommandFinished = new Emitter(); + private readonly _onCommandFinished = this._register(new Emitter()); readonly onCommandFinished = this._onCommandFinished.event; - private readonly _onCommandExecuted = new Emitter(); + private readonly _onCommandExecuted = this._register(new Emitter()); readonly onCommandExecuted = this._onCommandExecuted.event; - private readonly _onCommandInvalidated = new Emitter(); + private readonly _onCommandInvalidated = this._register(new Emitter()); readonly onCommandInvalidated = this._onCommandInvalidated.event; - private readonly _onCurrentCommandInvalidated = new Emitter(); + private readonly _onCurrentCommandInvalidated = this._register(new Emitter()); readonly onCurrentCommandInvalidated = this._onCurrentCommandInvalidated.event; constructor( private readonly _terminal: Terminal, private readonly _logService: ILogService ) { + super(); this._dimensions = { cols: this._terminal.cols, rows: this._terminal.rows }; - this._terminal.onResize(e => this._handleResize(e)); - this._terminal.onCursorMove(() => this._handleCursorMove()); + this._register(this._terminal.onResize(e => this._handleResize(e))); + this._register(this._terminal.onCursorMove(() => this._handleCursorMove())); this._setupClearListeners(); } diff --git a/src/vs/platform/terminal/common/capabilities/cwdDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/cwdDetectionCapability.ts index 7e3fcfa4a5d..96417aca6fc 100644 --- a/src/vs/platform/terminal/common/capabilities/cwdDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/cwdDetectionCapability.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; import { ICwdDetectionCapability, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; -export class CwdDetectionCapability implements ICwdDetectionCapability { +export class CwdDetectionCapability extends Disposable implements ICwdDetectionCapability { readonly type = TerminalCapability.CwdDetection; private _cwd = ''; private _cwds = new Map(); @@ -18,7 +19,7 @@ export class CwdDetectionCapability implements ICwdDetectionCapability { return Array.from(this._cwds.keys()); } - private readonly _onDidChangeCwd = new Emitter(); + private readonly _onDidChangeCwd = this._register(new Emitter()); readonly onDidChangeCwd = this._onDidChangeCwd.event; getCwd(): string { diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index a537f4ea557..16a760d57b1 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -500,7 +500,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati protected _createOrGetCwdDetection(): ICwdDetectionCapability { let cwdDetection = this.capabilities.get(TerminalCapability.CwdDetection); if (!cwdDetection) { - cwdDetection = new CwdDetectionCapability(); + cwdDetection = this._register(new CwdDetectionCapability()); this.capabilities.add(TerminalCapability.CwdDetection, cwdDetection); } return cwdDetection; @@ -509,7 +509,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati protected _createOrGetCommandDetection(terminal: Terminal): ICommandDetectionCapability { let commandDetection = this.capabilities.get(TerminalCapability.CommandDetection); if (!commandDetection) { - commandDetection = new CommandDetectionCapability(terminal, this._logService); + commandDetection = this._register(new CommandDetectionCapability(terminal, this._logService)); this.capabilities.add(TerminalCapability.CommandDetection, commandDetection); } return commandDetection; @@ -518,7 +518,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati protected _createOrGetBufferMarkDetection(terminal: Terminal): IBufferMarkCapability { let bufferMarkDetection = this.capabilities.get(TerminalCapability.BufferMarkDetection); if (!bufferMarkDetection) { - bufferMarkDetection = new BufferMarkCapability(terminal); + bufferMarkDetection = this._register(new BufferMarkCapability(terminal)); this.capabilities.add(TerminalCapability.BufferMarkDetection, bufferMarkDetection); } return bufferMarkDetection; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index 1837ce21cb4..39d3a0ceb03 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -19,6 +19,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IXtermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; import { IShellLaunchConfig } from 'vs/platform/terminal/common/terminal'; import { isLinux, isWindows } from 'vs/base/common/platform'; +import { Disposable } from 'vs/base/common/lifecycle'; const enum FontConstants { MinimumFontSize = 6, @@ -29,7 +30,7 @@ const enum FontConstants { * Encapsulates terminal configuration logic, the primary purpose of this file is so that platform * specific test cases can be written. */ -export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { +export class TerminalConfigHelper extends Disposable implements IBrowserTerminalConfigHelper { panelContainer: HTMLElement | undefined; private _charMeasureElement: HTMLElement | undefined; @@ -37,7 +38,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { protected _linuxDistro: LinuxDistro = LinuxDistro.Unknown; config!: ITerminalConfiguration; - private readonly _onConfigChanged = new Emitter(); + private readonly _onConfigChanged = this._register(new Emitter()); get onConfigChanged(): Event { return this._onConfigChanged.event; } constructor( @@ -47,6 +48,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { @IInstantiationService private readonly _instantiationService: IInstantiationService, @IProductService private readonly _productService: IProductService, ) { + super(); this._updateConfig(); this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(TERMINAL_CONFIG_SECTION)) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts index 02217becd6a..50d3e14ba2a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts @@ -33,15 +33,15 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor private _editorInputs: Map = new Map(); private _instanceDisposables: Map = new Map(); - private readonly _onDidDisposeInstance = new Emitter(); + private readonly _onDidDisposeInstance = this._register(new Emitter()); readonly onDidDisposeInstance = this._onDidDisposeInstance.event; - private readonly _onDidFocusInstance = new Emitter(); + private readonly _onDidFocusInstance = this._register(new Emitter()); readonly onDidFocusInstance = this._onDidFocusInstance.event; - private readonly _onDidChangeInstanceCapability = new Emitter(); + private readonly _onDidChangeInstanceCapability = this._register(new Emitter()); readonly onDidChangeInstanceCapability = this._onDidChangeInstanceCapability.event; - private readonly _onDidChangeActiveInstance = new Emitter(); + private readonly _onDidChangeActiveInstance = this._register(new Emitter()); readonly onDidChangeActiveInstance = this._onDidChangeActiveInstance.event; - private readonly _onDidChangeInstances = new Emitter(); + private readonly _onDidChangeInstances = this._register(new Emitter()); readonly onDidChangeInstances = this._onDidChangeInstances.event; constructor( diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index fd7ecfa56b2..f786225c38d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -278,9 +278,9 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { readonly onDisposed = this._onDisposed.event; private readonly _onInstancesChanged: Emitter = this._register(new Emitter()); readonly onInstancesChanged = this._onInstancesChanged.event; - private readonly _onDidChangeActiveInstance = new Emitter(); + private readonly _onDidChangeActiveInstance = this._register(new Emitter()); readonly onDidChangeActiveInstance = this._onDidChangeActiveInstance.event; - private readonly _onPanelOrientationChanged = new Emitter(); + private readonly _onPanelOrientationChanged = this._register(new Emitter()); readonly onPanelOrientationChanged = this._onPanelOrientationChanged.event; constructor( @@ -380,13 +380,6 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { removeInstance(instance: ITerminalInstance) { this._removeInstance(instance); - - // Dispose instance event listeners - const disposables = this._instanceDisposables.get(instance.instanceId); - if (disposables) { - dispose(disposables); - this._instanceDisposables.delete(instance.instanceId); - } } private _removeInstance(instance: ITerminalInstance) { @@ -418,6 +411,13 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { } else { this._onInstancesChanged.fire(); } + + // Dispose instance event listeners + const disposables = this._instanceDisposables.get(instance.instanceId); + if (disposables) { + dispose(disposables); + this._instanceDisposables.delete(instance.instanceId); + } } moveInstance(instance: ITerminalInstance, index: number): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts index 990072a3570..d321432ae2a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts @@ -33,27 +33,27 @@ export class TerminalGroupService extends Disposable implements ITerminalGroupSe private _container: HTMLElement | undefined; - private readonly _onDidChangeActiveGroup = new Emitter(); + private readonly _onDidChangeActiveGroup = this._register(new Emitter()); readonly onDidChangeActiveGroup = this._onDidChangeActiveGroup.event; - private readonly _onDidDisposeGroup = new Emitter(); + private readonly _onDidDisposeGroup = this._register(new Emitter()); readonly onDidDisposeGroup = this._onDidDisposeGroup.event; - private readonly _onDidChangeGroups = new Emitter(); + private readonly _onDidChangeGroups = this._register(new Emitter()); readonly onDidChangeGroups = this._onDidChangeGroups.event; - private readonly _onDidShow = new Emitter(); + private readonly _onDidShow = this._register(new Emitter()); readonly onDidShow = this._onDidShow.event; - private readonly _onDidDisposeInstance = new Emitter(); + private readonly _onDidDisposeInstance = this._register(new Emitter()); readonly onDidDisposeInstance = this._onDidDisposeInstance.event; - private readonly _onDidFocusInstance = new Emitter(); + private readonly _onDidFocusInstance = this._register(new Emitter()); readonly onDidFocusInstance = this._onDidFocusInstance.event; - private readonly _onDidChangeActiveInstance = new Emitter(); + private readonly _onDidChangeActiveInstance = this._register(new Emitter()); readonly onDidChangeActiveInstance = this._onDidChangeActiveInstance.event; - private readonly _onDidChangeInstances = new Emitter(); + private readonly _onDidChangeInstances = this._register(new Emitter()); readonly onDidChangeInstances = this._onDidChangeInstances.event; - private readonly _onDidChangeInstanceCapability = new Emitter(); + private readonly _onDidChangeInstanceCapability = this._register(new Emitter()); readonly onDidChangeInstanceCapability = this._onDidChangeInstanceCapability.event; - private readonly _onDidChangePanelOrientation = new Emitter(); + private readonly _onDidChangePanelOrientation = this._register(new Emitter()); readonly onDidChangePanelOrientation = this._onDidChangePanelOrientation.event; constructor( diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 3d65f2cf44b..be186d2e2ad 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -17,7 +17,7 @@ import { ErrorNoTelemetry, onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ISeparator, template } from 'vs/base/common/labels'; -import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, MutableDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import * as path from 'vs/base/common/path'; import { OS, OperatingSystem, isMacintosh, isWindows } from 'vs/base/common/platform'; @@ -169,9 +169,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _containerReadyBarrier: AutoOpenBarrier; private _attachBarrier: AutoOpenBarrier; private _icon: TerminalIcon | undefined; - private _messageTitleDisposable: IDisposable | undefined; + private _messageTitleDisposable: MutableDisposable = this._register(new MutableDisposable()); private _widgetManager: TerminalWidgetManager = new TerminalWidgetManager(); - private _dndObserver: IDisposable | undefined; + private _dndObserver: MutableDisposable = this._register(new MutableDisposable()); private _lastLayoutDimensions: dom.Dimension | undefined; private _hasHadInput: boolean; private _description?: string; @@ -246,7 +246,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { get exitCode(): number | undefined { return this._exitCode; } get exitReason(): TerminalExitReason | undefined { return this._exitReason; } get hadFocusOnExit(): boolean { return this._hadFocusOnExit; } - get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; } + get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable.value; } get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; } get shellType(): TerminalShellType | undefined { return this._shellType; } get os(): OperatingSystem | undefined { return this._processManager.os; } @@ -451,8 +451,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._initDimensions(); this._processManager = this._createProcessManager(); - this._register(toDisposable(() => this._dndObserver?.dispose())); - this._containerReadyBarrier = new AutoOpenBarrier(Constants.WaitForContainerThreshold); this._attachBarrier = new AutoOpenBarrier(1000); this._xtermReadyPromise = this._createXterm(); @@ -528,10 +526,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { e.affectsConfiguration(TerminalSettingId.TerminalTitle) || e.affectsConfiguration(TerminalSettingId.TerminalTitleSeparator) || e.affectsConfiguration(TerminalSettingId.TerminalDescription)) { - this._labelComputer?.refreshLabel(); + this._labelComputer?.refreshLabel(this); } })); - this._register(this._workspaceContextService.onDidChangeWorkspaceFolders(() => this._labelComputer?.refreshLabel())); + this._register(this._workspaceContextService.onDidChangeWorkspaceFolders(() => this._labelComputer?.refreshLabel(this))); this._register(this.onDidBlur(() => this.xterm?.suggestController?.hideSuggestWidget())); // Clear out initial data events after 10 seconds, hopefully extension hosts are up and @@ -565,6 +563,14 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); this.onDisposed(() => { contribution.dispose(); + this._contributions.delete(desc.id); + // Just in case to prevent potential future memory leaks due to cyclic dependency. + if ('instance' in contribution) { + delete contribution.instance; + } + if ('_instance' in contribution) { + delete contribution._instance; + } }); } } @@ -746,13 +752,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Write initial text, deferring onLineFeed listener when applicable to avoid firing // onLineData events containing initialText const initialTextWrittenPromise = this._shellLaunchConfig.initialText ? new Promise(r => this._writeInitialText(xterm, r)) : undefined; - const lineDataEventAddon = new LineDataEventAddon(initialTextWrittenPromise); + const lineDataEventAddon = this._register(new LineDataEventAddon(initialTextWrittenPromise)); lineDataEventAddon.onLineData(e => this._onLineData.fire(e)); this._lineDataEventAddon = lineDataEventAddon; // Delay the creation of the bell listener to avoid showing the bell when the terminal // starts up or reconnects setTimeout(() => { - xterm.raw.onBell(() => { + this._register(xterm.raw.onBell(() => { if (this._configHelper.config.enableBell) { this.statusList.add({ id: TerminalStatus.Bell, @@ -762,17 +768,17 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }, this._configHelper.config.bellDuration); this._audioCueService.playSound(AudioCue.terminalBell.sound.getSound()); } - }); + })); }, 1000); - xterm.raw.onSelectionChange(async () => this._onSelectionChange()); - xterm.raw.buffer.onBufferChange(() => this._refreshAltBufferContextKey()); + this._register(xterm.raw.onSelectionChange(async () => this._onSelectionChange())); + this._register(xterm.raw.buffer.onBufferChange(() => this._refreshAltBufferContextKey())); this._processManager.onProcessData(e => this._onProcessData(e)); - xterm.raw.onData(async data => { + this._register(xterm.raw.onData(async data => { await this._processManager.write(data); this._onDidInputData.fire(this); - }); - xterm.raw.onBinary(data => this._processManager.processBinary(data)); + })); + this._register(xterm.raw.onBinary(data => this._processManager.processBinary(data))); // Init winpty compat and link handler after process creation as they rely on the // underlying process OS this._processManager.onProcessReady(async (processTraits) => { @@ -1047,14 +1053,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } private _initDragAndDrop(container: HTMLElement) { - this._dndObserver?.dispose(); - const dndController = this._scopedInstantiationService.createInstance(TerminalInstanceDragAndDropController, container); + const dndController = this._register(this._scopedInstantiationService.createInstance(TerminalInstanceDragAndDropController, container)); dndController.onDropTerminal(e => this._onRequestAddInstanceToGroup.fire(e)); dndController.onDropFile(async path => { this.focus(); await this.sendPath(path, false); }); - this._dndObserver = new dom.DragAndDropObserver(container, dndController); + this._dndObserver.value = new dom.DragAndDropObserver(container, dndController); } hasSelection(): boolean { @@ -1340,12 +1345,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Set the initial name based on the _resolved_ shell launch config, this will also // ensure the resolved icon gets shown if (!this._labelComputer) { - this._labelComputer = this._register(this._scopedInstantiationService.createInstance(TerminalLabelComputer, this._configHelper, this)); - this._labelComputer.onDidChangeLabel(e => { + this._labelComputer = this._register(this._scopedInstantiationService.createInstance(TerminalLabelComputer, this._configHelper)); + this._register(this._labelComputer.onDidChangeLabel(e => { this._title = e.title; this._description = e.description; this._onTitleChanged.fire(this); - }); + })); } if (this._shellLaunchConfig.name) { this._setTitle(this._shellLaunchConfig.name, TitleEventSource.Api); @@ -1354,7 +1359,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // _xtermReadyPromise is ready constructed since this is called from the ctor setTimeout(() => { this._xtermReadyPromise.then(xterm => { - this._messageTitleDisposable = xterm.raw.onTitleChange(e => this._onTitleChange(e)); + this._messageTitleDisposable.value = xterm.raw.onTitleChange(e => this._onTitleChange(e)); }); }); this._setTitle(this._shellLaunchConfig.executable, TitleEventSource.Process); @@ -1365,7 +1370,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { switch (type) { case ProcessPropertyType.Cwd: this._cwd = value; - this._labelComputer?.refreshLabel(); + this._labelComputer?.refreshLabel(this); break; case ProcessPropertyType.InitialCwd: this._initialCwd = value; @@ -1913,8 +1918,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // If the title has not been set by the API or the rename command, unregister the handler that // automatically updates the terminal name this._staticTitle = title; - dispose(this._messageTitleDisposable); - this._messageTitleDisposable = undefined; + this._messageTitleDisposable.value = undefined; break; case TitleEventSource.Sequence: // On Windows, some shells will fire this with the full path which we want to trim @@ -1959,7 +1963,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } this._fixedCols = this._parseFixedDimension(cols); - this._labelComputer?.refreshLabel(); + this._labelComputer?.refreshLabel(this); this._terminalHasFixedWidth.set(!!this._fixedCols); const rows = await this._quickInputService.input({ title: nls.localize('setTerminalDimensionsRow', "Set Fixed Dimensions: Row"), @@ -1970,7 +1974,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } this._fixedRows = this._parseFixedDimension(rows); - this._labelComputer?.refreshLabel(); + this._labelComputer?.refreshLabel(this); await this._refreshScrollbar(); this._resize(); this.focus(); @@ -2008,7 +2012,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } await this._refreshScrollbar(); - this._labelComputer?.refreshLabel(); + this._labelComputer?.refreshLabel(this); this.focus(); } @@ -2152,7 +2156,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { title = this._updateTitleProperties(title, eventSource); const titleChanged = title !== this._title; this._title = title; - this._labelComputer?.refreshLabel(reset); + this._labelComputer?.refreshLabel(this, reset); this._setAriaLabel(this.xterm?.raw, this._instanceId, this._title); if (titleChanged) { @@ -2251,9 +2255,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { class TerminalInstanceDragAndDropController extends Disposable implements dom.IDragAndDropObserverCallbacks { private _dropOverlay?: HTMLElement; - private readonly _onDropFile = new Emitter(); + private readonly _onDropFile = this._register(new Emitter()); get onDropFile(): Event { return this._onDropFile.event; } - private readonly _onDropTerminal = new Emitter(); + private readonly _onDropTerminal = this._register(new Emitter()); get onDropTerminal(): Event { return this._onDropTerminal.event; } constructor( @@ -2405,65 +2409,65 @@ export class TerminalLabelComputer extends Disposable { constructor( private readonly _configHelper: TerminalConfigHelper, - private readonly _instance: Pick, @IFileService private readonly _fileService: IFileService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService ) { super(); } - refreshLabel(reset?: boolean): void { - this._title = this.computeLabel(this._configHelper.config.tabs.title, TerminalLabelType.Title, reset); - this._description = this.computeLabel(this._configHelper.config.tabs.description, TerminalLabelType.Description); - if (this._title !== this._instance.title || this._description !== this._instance.description || reset) { + refreshLabel(instance: Pick, reset?: boolean): void { + this._title = this.computeLabel(instance, this._configHelper.config.tabs.title, TerminalLabelType.Title, reset); + this._description = this.computeLabel(instance, this._configHelper.config.tabs.description, TerminalLabelType.Description); + if (this._title !== instance.title || this._description !== instance.description || reset) { this._onDidChangeLabel.fire({ title: this._title, description: this._description }); } } computeLabel( + instance: Pick, labelTemplate: string, labelType: TerminalLabelType, reset?: boolean ) { - const type = this._instance.shellLaunchConfig.attachPersistentProcess?.type || this._instance.shellLaunchConfig.type; + const type = instance.shellLaunchConfig.attachPersistentProcess?.type || instance.shellLaunchConfig.type; const templateProperties: ITerminalLabelTemplateProperties = { - cwd: this._instance.cwd || this._instance.initialCwd || '', + cwd: instance.cwd || instance.initialCwd || '', cwdFolder: '', - workspaceFolder: this._instance.workspaceFolder ? path.basename(this._instance.workspaceFolder.uri.fsPath) : undefined, + workspaceFolder: instance.workspaceFolder ? path.basename(instance.workspaceFolder.uri.fsPath) : undefined, local: type === 'Local' ? type : undefined, - process: this._instance.processName, - sequence: this._instance.sequence, + process: instance.processName, + sequence: instance.sequence, task: type === 'Task' ? type : undefined, - fixedDimensions: this._instance.fixedCols - ? (this._instance.fixedRows ? `\u2194${this._instance.fixedCols} \u2195${this._instance.fixedRows}` : `\u2194${this._instance.fixedCols}`) - : (this._instance.fixedRows ? `\u2195${this._instance.fixedRows}` : ''), + fixedDimensions: instance.fixedCols + ? (instance.fixedRows ? `\u2194${instance.fixedCols} \u2195${instance.fixedRows}` : `\u2194${instance.fixedCols}`) + : (instance.fixedRows ? `\u2195${instance.fixedRows}` : ''), separator: { label: this._configHelper.config.tabs.separator } }; labelTemplate = labelTemplate.trim(); if (!labelTemplate) { - return labelType === TerminalLabelType.Title ? (this._instance.processName || '') : ''; + return labelType === TerminalLabelType.Title ? (instance.processName || '') : ''; } - if (!reset && this._instance.staticTitle && labelType === TerminalLabelType.Title) { - return this._instance.staticTitle.replace(/[\n\r\t]/g, '') || templateProperties.process?.replace(/[\n\r\t]/g, '') || ''; + if (!reset && instance.staticTitle && labelType === TerminalLabelType.Title) { + return instance.staticTitle.replace(/[\n\r\t]/g, '') || templateProperties.process?.replace(/[\n\r\t]/g, '') || ''; } - const detection = this._instance.capabilities.has(TerminalCapability.CwdDetection) || this._instance.capabilities.has(TerminalCapability.NaiveCwdDetection); + const detection = instance.capabilities.has(TerminalCapability.CwdDetection) || instance.capabilities.has(TerminalCapability.NaiveCwdDetection); const folders = this._workspaceContextService.getWorkspace().folders; const multiRootWorkspace = folders.length > 1; // Only set cwdFolder if detection is on - if (templateProperties.cwd && detection && (!this._instance.shellLaunchConfig.isFeatureTerminal || labelType === TerminalLabelType.Title)) { + if (templateProperties.cwd && detection && (!instance.shellLaunchConfig.isFeatureTerminal || labelType === TerminalLabelType.Title)) { const cwdUri = URI.from({ - scheme: this._instance.workspaceFolder?.uri.scheme || Schemas.file, - path: this._instance.cwd ? path.resolve(this._instance.cwd) : undefined + scheme: instance.workspaceFolder?.uri.scheme || Schemas.file, + path: instance.cwd ? path.resolve(instance.cwd) : undefined }); // Multi-root workspaces always show cwdFolder to disambiguate them, otherwise only show // when it differs from the workspace folder in which it was launched from let showCwd = false; if (multiRootWorkspace) { showCwd = true; - } else if (this._instance.workspaceFolder?.uri) { - const caseSensitive = this._fileService.hasCapability(this._instance.workspaceFolder.uri, FileSystemProviderCapabilities.PathCaseSensitive); - showCwd = cwdUri.fsPath.localeCompare(this._instance.workspaceFolder.uri.fsPath, undefined, { sensitivity: caseSensitive ? 'case' : 'base' }) !== 0; + } else if (instance.workspaceFolder?.uri) { + const caseSensitive = this._fileService.hasCapability(instance.workspaceFolder.uri, FileSystemProviderCapabilities.PathCaseSensitive); + showCwd = cwdUri.fsPath.localeCompare(instance.workspaceFolder.uri.fsPath, undefined, { sensitivity: caseSensitive ? 'case' : 'base' }) !== 0; } if (showCwd) { @@ -2473,7 +2477,7 @@ export class TerminalLabelComputer extends Disposable { // Remove special characters that could mess with rendering const label = template(labelTemplate, (templateProperties as unknown) as { [key: string]: string | ISeparator | undefined | null }).replace(/[\n\r\t]/g, '').trim(); - return label === '' && labelType === TerminalLabelType.Title ? (this._instance.processName || '') : label; + return label === '' && labelType === TerminalLabelType.Title ? (instance.processName || '') : label; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index ffa9c55f759..d341cee3178 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -25,7 +25,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst private _configHelper: TerminalConfigHelper; private _backendRegistration = new Map; resolve: () => void }>(); - private readonly _onDidCreateInstance = new Emitter(); + private readonly _onDidCreateInstance = this._register(new Emitter()); get onDidCreateInstance(): Event { return this._onDidCreateInstance.event; } constructor( diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts index d161a40cb6e..e91c102646f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts @@ -7,7 +7,7 @@ import { equals } from 'vs/base/common/arrays'; import { AutoOpenBarrier } from 'vs/base/common/async'; import { throttle } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { isMacintosh, isWeb, isWindows, OperatingSystem, OS } from 'vs/base/common/platform'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -27,7 +27,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA * Links TerminalService with TerminalProfileResolverService * and keeps the available terminal profiles updated */ -export class TerminalProfileService implements ITerminalProfileService { +export class TerminalProfileService extends Disposable implements ITerminalProfileService { declare _serviceBrand: undefined; private _webExtensionContributedProfileContextKey: IContextKey; @@ -39,7 +39,7 @@ export class TerminalProfileService implements ITerminalProfileService { private _platformConfigJustRefreshed = false; private readonly _profileProviders: Map> = new Map(); - private readonly _onDidChangeAvailableProfiles = new Emitter(); + private readonly _onDidChangeAvailableProfiles = this._register(new Emitter()); get onDidChangeAvailableProfiles(): Event { return this._onDidChangeAvailableProfiles.event; } get profilesReady(): Promise { return this._profilesReadyPromise; } @@ -62,6 +62,8 @@ export class TerminalProfileService implements ITerminalProfileService { @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService ) { + super(); + // in web, we don't want to show the dropdown unless there's a web extension // that contributes a profile this._extensionService.onDidChangeExtensions(() => this.refreshAvailableProfiles()); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 27ff2a40b23..23b4cbdfd3d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -7,7 +7,7 @@ import * as dom from 'vs/base/browser/dom'; import { DeferredPromise, timeout } from 'vs/base/common/async'; import { debounce } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; -import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; @@ -53,7 +53,7 @@ import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilitie import { ITimerService } from 'vs/workbench/services/timer/browser/timerService'; import { mark } from 'vs/base/common/performance'; -export class TerminalService implements ITerminalService { +export class TerminalService extends Disposable implements ITerminalService { declare _serviceBrand: undefined; private _hostActiveTerminals: Map = new Map(); @@ -120,47 +120,47 @@ export class TerminalService implements ITerminalService { private _editingTerminal: ITerminalInstance | undefined; - private readonly _onDidChangeActiveGroup = new Emitter(); + private readonly _onDidChangeActiveGroup = this._register(new Emitter()); get onDidChangeActiveGroup(): Event { return this._onDidChangeActiveGroup.event; } - private readonly _onDidCreateInstance = new Emitter(); + private readonly _onDidCreateInstance = this._register(new Emitter()); get onDidCreateInstance(): Event { return this._onDidCreateInstance.event; } - private readonly _onDidDisposeInstance = new Emitter(); + private readonly _onDidDisposeInstance = this._register(new Emitter()); get onDidDisposeInstance(): Event { return this._onDidDisposeInstance.event; } - private readonly _onDidFocusInstance = new Emitter(); + private readonly _onDidFocusInstance = this._register(new Emitter()); get onDidFocusInstance(): Event { return this._onDidFocusInstance.event; } - private readonly _onDidReceiveProcessId = new Emitter(); + private readonly _onDidReceiveProcessId = this._register(new Emitter()); get onDidReceiveProcessId(): Event { return this._onDidReceiveProcessId.event; } - private readonly _onDidRequestStartExtensionTerminal = new Emitter(); + private readonly _onDidRequestStartExtensionTerminal = this._register(new Emitter()); get onDidRequestStartExtensionTerminal(): Event { return this._onDidRequestStartExtensionTerminal.event; } - private readonly _onDidChangeInstanceDimensions = new Emitter(); + private readonly _onDidChangeInstanceDimensions = this._register(new Emitter()); get onDidChangeInstanceDimensions(): Event { return this._onDidChangeInstanceDimensions.event; } - private readonly _onDidMaxiumumDimensionsChange = new Emitter(); + private readonly _onDidMaxiumumDimensionsChange = this._register(new Emitter()); get onDidMaximumDimensionsChange(): Event { return this._onDidMaxiumumDimensionsChange.event; } - private readonly _onDidChangeInstanceCapability = new Emitter(); + private readonly _onDidChangeInstanceCapability = this._register(new Emitter()); get onDidChangeInstanceCapability(): Event { return this._onDidChangeInstanceCapability.event; } - private readonly _onDidChangeInstances = new Emitter(); + private readonly _onDidChangeInstances = this._register(new Emitter()); get onDidChangeInstances(): Event { return this._onDidChangeInstances.event; } - private readonly _onDidChangeInstanceTitle = new Emitter(); + private readonly _onDidChangeInstanceTitle = this._register(new Emitter()); get onDidChangeInstanceTitle(): Event { return this._onDidChangeInstanceTitle.event; } - private readonly _onDidChangeInstanceIcon = new Emitter<{ instance: ITerminalInstance; userInitiated: boolean }>(); + private readonly _onDidChangeInstanceIcon = this._register(new Emitter<{ instance: ITerminalInstance; userInitiated: boolean }>()); get onDidChangeInstanceIcon(): Event<{ instance: ITerminalInstance; userInitiated: boolean }> { return this._onDidChangeInstanceIcon.event; } - private readonly _onDidChangeInstanceColor = new Emitter<{ instance: ITerminalInstance; userInitiated: boolean }>(); + private readonly _onDidChangeInstanceColor = this._register(new Emitter<{ instance: ITerminalInstance; userInitiated: boolean }>()); get onDidChangeInstanceColor(): Event<{ instance: ITerminalInstance; userInitiated: boolean }> { return this._onDidChangeInstanceColor.event; } - private readonly _onDidChangeActiveInstance = new Emitter(); + private readonly _onDidChangeActiveInstance = this._register(new Emitter()); get onDidChangeActiveInstance(): Event { return this._onDidChangeActiveInstance.event; } - private readonly _onDidChangeInstancePrimaryStatus = new Emitter(); + private readonly _onDidChangeInstancePrimaryStatus = this._register(new Emitter()); get onDidChangeInstancePrimaryStatus(): Event { return this._onDidChangeInstancePrimaryStatus.event; } - private readonly _onDidInputInstanceData = new Emitter(); + private readonly _onDidInputInstanceData = this._register(new Emitter()); get onDidInputInstanceData(): Event { return this._onDidInputInstanceData.event; } - private readonly _onDidChangeSelection = new Emitter(); + private readonly _onDidChangeSelection = this._register(new Emitter()); get onDidChangeSelection(): Event { return this._onDidChangeSelection.event; } - private readonly _onDidDisposeGroup = new Emitter(); + private readonly _onDidDisposeGroup = this._register(new Emitter()); get onDidDisposeGroup(): Event { return this._onDidDisposeGroup.event; } - private readonly _onDidChangeGroups = new Emitter(); + private readonly _onDidChangeGroups = this._register(new Emitter()); get onDidChangeGroups(): Event { return this._onDidChangeGroups.event; } - private readonly _onDidRegisterProcessSupport = new Emitter(); + private readonly _onDidRegisterProcessSupport = this._register(new Emitter()); get onDidRegisterProcessSupport(): Event { return this._onDidRegisterProcessSupport.event; } - private readonly _onDidChangeConnectionState = new Emitter(); + private readonly _onDidChangeConnectionState = this._register(new Emitter()); get onDidChangeConnectionState(): Event { return this._onDidChangeConnectionState.event; } constructor( @@ -185,7 +185,9 @@ export class TerminalService implements ITerminalService { @IKeybindingService private readonly _keybindingService: IKeybindingService, @ITimerService private readonly _timerService: ITimerService ) { - this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper); + super(); + + this._configHelper = this._register(this._instantiationService.createInstance(TerminalConfigHelper)); // the below avoids having to poll routinely. // we update detected profiles when an instance is created so that, // for example, we detect if you've installed a pwsh diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 4a3fb064e99..0991297c735 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -46,7 +46,7 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin import { getTerminalResourcesFromDragEvent, parseTerminalUri } from 'vs/workbench/contrib/terminal/browser/terminalUri'; import { getInstanceHoverInfo } from 'vs/workbench/contrib/terminal/browser/terminalTooltip'; import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; import { getColorForSeverity } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; @@ -196,8 +196,8 @@ export class TerminalTabList extends WorkbenchList { } }); if (!this._decorationsProvider) { - this._decorationsProvider = instantiationService.createInstance(TabDecorationsProvider); - decorationsService.registerDecorationsProvider(this._decorationsProvider); + this._decorationsProvider = this.disposables.add(instantiationService.createInstance(TabDecorationsProvider)); + this.disposables.add(decorationsService.registerDecorationsProvider(this._decorationsProvider)); } this.refresh(); } @@ -737,18 +737,17 @@ class TerminalTabsDragAndDrop implements IListDragAndDrop { } } -class TabDecorationsProvider implements IDecorationsProvider { +class TabDecorationsProvider extends Disposable implements IDecorationsProvider { readonly label: string = localize('label', "Terminal"); - private readonly _onDidChange = new Emitter(); + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; constructor( @ITerminalService private readonly _terminalService: ITerminalService ) { - this._terminalService.onDidChangeInstancePrimaryStatus(e => this._onDidChange.fire([e.resource])); - } - - get onDidChange(): Event { - return this._onDidChange.event; + super(); + this._register(this._terminalService.onDidChangeInstancePrimaryStatus(e => this._onDidChange.fire([e.resource]))); } provideDecorations(resource: URI): IDecorationData | undefined { @@ -772,8 +771,4 @@ class TabDecorationsProvider implements IDecorationsProvider { tooltip: primaryStatus.tooltip }; } - - dispose(): void { - this.dispose(); - } } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/suggestAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/suggestAddon.ts index 362f70dcde8..7ff72cf1c5a 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/suggestAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/suggestAddon.ts @@ -83,9 +83,9 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest private _cursorIndexDelta: number = 0; private _inputQueue?: string[]; - private readonly _onBell = new Emitter(); + private readonly _onBell = this._register(new Emitter()); readonly onBell = this._onBell.event; - private readonly _onAcceptedCompletion = new Emitter(); + private readonly _onAcceptedCompletion = this._register(new Emitter()); readonly onAcceptedCompletion = this._onAcceptedCompletion.event; constructor( diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index d5acf93a0d8..8568498af91 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -146,21 +146,21 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, ID get isStdinDisabled(): boolean { return !!this.raw.options.disableStdin; } - private readonly _onDidRequestRunCommand = new Emitter<{ command: ITerminalCommand; copyAsHtml?: boolean; noNewLine?: boolean }>(); + private readonly _onDidRequestRunCommand = this.add(new Emitter<{ command: ITerminalCommand; copyAsHtml?: boolean; noNewLine?: boolean }>()); readonly onDidRequestRunCommand = this._onDidRequestRunCommand.event; - private readonly _onDidRequestFocus = new Emitter(); + private readonly _onDidRequestFocus = this.add(new Emitter()); readonly onDidRequestFocus = this._onDidRequestFocus.event; - private readonly _onDidRequestSendText = new Emitter(); + private readonly _onDidRequestSendText = this.add(new Emitter()); readonly onDidRequestSendText = this._onDidRequestSendText.event; - private readonly _onDidRequestFreePort = new Emitter(); + private readonly _onDidRequestFreePort = this.add(new Emitter()); readonly onDidRequestFreePort = this._onDidRequestFreePort.event; - private readonly _onDidChangeFindResults = new Emitter<{ resultIndex: number; resultCount: number }>(); + private readonly _onDidChangeFindResults = this.add(new Emitter<{ resultIndex: number; resultCount: number }>()); readonly onDidChangeFindResults = this._onDidChangeFindResults.event; - private readonly _onDidChangeSelection = new Emitter(); + private readonly _onDidChangeSelection = this.add(new Emitter()); readonly onDidChangeSelection = this._onDidChangeSelection.event; - private readonly _onDidChangeFocus = new Emitter(); + private readonly _onDidChangeFocus = this.add(new Emitter()); readonly onDidChangeFocus = this._onDidChangeFocus.event; - private readonly _onDidDispose = new Emitter(); + private readonly _onDidDispose = this.add(new Emitter()); readonly onDidDispose = this._onDidDispose.event; get markTracker(): IMarkTracker { return this._markNavigationAddon; } diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts index 8ae63dab664..0aaa48f6b05 100644 --- a/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts +++ b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts @@ -12,6 +12,7 @@ import { deserializeEnvironmentDescriptionMap, deserializeEnvironmentVariableCol import { IEnvironmentVariableCollectionWithPersistence, IEnvironmentVariableService } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { TerminalStorageKeys } from 'vs/workbench/contrib/terminal/common/terminalStorageKeys'; import { IMergedEnvironmentVariableCollection, ISerializableEnvironmentDescriptionMap, ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; +import { Disposable } from 'vs/base/common/lifecycle'; interface ISerializableExtensionEnvironmentVariableCollection { extensionIdentifier: string; @@ -22,19 +23,21 @@ interface ISerializableExtensionEnvironmentVariableCollection { /** * Tracks and persists environment variable collections as defined by extensions. */ -export class EnvironmentVariableService implements IEnvironmentVariableService { +export class EnvironmentVariableService extends Disposable implements IEnvironmentVariableService { declare readonly _serviceBrand: undefined; collections: Map = new Map(); mergedCollection: IMergedEnvironmentVariableCollection; - private readonly _onDidChangeCollections = new Emitter(); + private readonly _onDidChangeCollections = this._register(new Emitter()); get onDidChangeCollections(): Event { return this._onDidChangeCollections.event; } constructor( @IExtensionService private readonly _extensionService: IExtensionService, @IStorageService private readonly _storageService: IStorageService ) { + super(); + this._storageService.remove(TerminalStorageKeys.DeprecatedEnvironmentVariableCollections, StorageScope.WORKSPACE); const serializedPersistedCollections = this._storageService.get(TerminalStorageKeys.EnvironmentVariableCollections, StorageScope.WORKSPACE); if (serializedPersistedCollections) { From 9569901a94604110f96713d6da67815547bd2451 Mon Sep 17 00:00:00 2001 From: weartist Date: Tue, 8 Aug 2023 14:22:12 +0800 Subject: [PATCH 09/69] adapt setting editor to support navigating input / result --- .../preferences/browser/settingsEditor2.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 4f11c22645c..ed0458caae9 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -66,6 +66,8 @@ import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { IProductService } from 'vs/platform/product/common/productService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; + export const enum SettingsFocusContext { Search, @@ -335,6 +337,20 @@ export class SettingsEditor2 extends EditorPane { this.createBody(this.rootElement); this.addCtrlAInterceptor(this.rootElement); this.updateStyles(); + + this._register(registerNavigableContainer({ + focusNotifiers: [this], + focusNextWidget: () => { + if (this.searchWidget.inputWidget.hasWidgetFocus()) { + this.focusSettings(); + } + }, + focusPreviousWidget: () => { + if (!this.searchWidget.inputWidget.hasWidgetFocus()) { + this.focusSearch(); + } + } + })); } override async setInput(input: SettingsEditor2Input, options: ISettingsEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { @@ -1861,3 +1877,6 @@ interface ISettingsEditor2State { searchQuery: string; target: SettingsTarget; } + + + From 57caf3bca9428458a5626334cfd93f6413d5b58e Mon Sep 17 00:00:00 2001 From: weartist Date: Tue, 8 Aug 2023 14:23:47 +0800 Subject: [PATCH 10/69] =?UTF-8?q?=F0=9F=92=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workbench/contrib/preferences/browser/settingsEditor2.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index ed0458caae9..f05db46a48a 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -1877,6 +1877,3 @@ interface ISettingsEditor2State { searchQuery: string; target: SettingsTarget; } - - - From 339afc23cb234b83c6260c74eb4f143b154180a0 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 8 Aug 2023 11:47:08 +0200 Subject: [PATCH 11/69] extract chatml things into its own proposal file, rename namespace from `llm` to `chat` --- .../workbench/api/common/extHost.api.impl.ts | 4 +-- .../contrib/chat/common/chatServiceImpl.ts | 8 +++--- .../common/extensionsApiProposals.ts | 1 + src/vscode-dts/vscode.proposed.chat.d.ts | 25 +++++++++++++++++++ .../vscode.proposed.chatProvider.d.ts | 19 +------------- .../vscode.proposed.chatRequestAccess.d.ts | 7 +++++- .../vscode.proposed.chatSlashCommands.d.ts | 4 +-- 7 files changed, 42 insertions(+), 26 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.chat.d.ts diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 7bb6f29b027..deeece7b083 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1333,7 +1333,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }; // namespace: llm - const llm: typeof vscode.llm = { + const chat: typeof vscode.chat = { registerChatResponseProvider(id: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata) { checkProposedApiEnabled(extension, 'chatProvider'); return extHostChatProvider.registerProvider(extension.identifier, id, provider, metadata); @@ -1361,7 +1361,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I interactive, interactiveSlashCommands, l10n, - llm, + chat, languages, notebooks, scm, diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 676823c2784..39571029c81 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -6,7 +6,7 @@ import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { MarkdownString } from 'vs/base/common/htmlContent'; +import { MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; import { Iterable } from 'vs/base/common/iterator'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; @@ -488,8 +488,10 @@ export class ChatService extends Disposable implements IChatService { if (typeof request.message !== 'string' || !request.response) { continue; } - history.push({ role: ChatMessageRole.User, content: request.message }); - history.push({ role: ChatMessageRole.Assistant, content: request.response?.response.value }); + if (isMarkdownString(request.response.response)) { + history.push({ role: ChatMessageRole.User, content: request.message }); + history.push({ role: ChatMessageRole.Assistant, content: request.response?.response.value }); + } } await this.chatSlashCommandService.executeCommand(resolvedCommand, message.substring(resolvedCommand.length + 1).trimStart(), new Progress(p => progressCallback(p)), history, token); rawResponse = { session: model.session! }; diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 4832cfc8bf8..19fe88c4ea3 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -9,6 +9,7 @@ export const allApiProposals = Object.freeze({ authGetSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authGetSessions.d.ts', authSession: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authSession.d.ts', canonicalUriProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts', + chat: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chat.d.ts', chatProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', chatRequestAccess: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts', chatSlashCommands: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chat.d.ts b/src/vscode-dts/vscode.proposed.chat.d.ts new file mode 100644 index 00000000000..8585cb31205 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.chat.d.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // ChatML + export enum ChatMessageRole { + System = 0, + User = 1, + Assistant = 2, + Function = 3, + } + + // ChatML + export class ChatMessage { + role: ChatMessageRole; + content: string; + name?: string; + + constructor(role: ChatMessageRole, content: string); + } + +} diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index e4d56810d76..944c3853435 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -5,23 +5,6 @@ declare module 'vscode' { - // ChatML - export enum ChatMessageRole { - System = 0, - User = 1, - Assistant = 2, - Function = 3, - } - - // ChatML - export class ChatMessage { - role: ChatMessageRole; - content: string; - name?: string; - - constructor(role: ChatMessageRole, content: string); - } - export interface ChatResponseFragment { index: number; part: string; @@ -39,7 +22,7 @@ declare module 'vscode' { name: string; } - export namespace llm { + export namespace chat { /** * Register a LLM as chat response provider to the editor. diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts index 45e15accb2a..bf6ac4ff236 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -5,6 +5,11 @@ declare module 'vscode' { + export interface ChatResponseFragment { + index: number; + part: string; + } + /** * Represents access to using a chat provider (LLM). Access is granted and temporary, usually only valid * for the duration of an user interaction. @@ -28,7 +33,7 @@ declare module 'vscode' { makeRequest(messages: ChatMessage[], options: { [name: string]: any }, progress: Progress, token: CancellationToken): Thenable; } - export namespace llm { + export namespace chat { /** * Request access to chat. diff --git a/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts b/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts index 53119d85a17..3be6ac89cc0 100644 --- a/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts +++ b/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts @@ -29,7 +29,7 @@ declare module 'vscode' { } export interface SlashResult { - followUp?: InteractiveSessionFollowup[]; + // followUp?: InteractiveSessionFollowup[]; } export interface SlashCommand { @@ -37,7 +37,7 @@ declare module 'vscode' { (prompt: ChatMessage, context: SlashCommandContext, progress: Progress, token: CancellationToken): Thenable; } - export namespace llm { + export namespace chat { export function registerSlashCommand(name: string, command: SlashCommand): Disposable; } } From 639429da5c80323b4b91d6c9f2603bd7779c15e9 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 8 Aug 2023 12:03:55 +0200 Subject: [PATCH 12/69] allow for dynamic slash command data registraion --- .../api/browser/mainThreadChatSlashCommands.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts b/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts index 839885602ed..dff1def81c7 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts @@ -29,6 +29,16 @@ export class MainThreadChatSlashCommands implements MainThreadChatSlashCommandsS } $registerCommand(handle: number, name: string): void { + + if (!this._chatSlashCommandService.hasCommand(name)) { + // dynamic slash commands! + this._chatSlashCommandService.registerSlashData({ + name, + id: name, + detail: '' + }); + } + const d = this._chatSlashCommandService.registerSlashCallback(name, async (prompt, progress, history, token) => { const requestId = Math.random(); this._pendingProgress.set(requestId, progress); From c448354dd7301277d8bfd68633487c1ce2366c37 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Aug 2023 09:43:40 -0700 Subject: [PATCH 13/69] allow context view to hide before showing next view so screen reader detects focus change --- .../accessibility/browser/accessibleView.ts | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 650211a7767..047c4ee89ab 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -365,17 +365,21 @@ class AccessibleView extends Disposable { const previousProvider = Object.assign({}, this._currentProvider); const accessibleViewHelpProvider = Object.assign({}, this._currentProvider); accessibleViewHelpProvider.options.type = AccessibleViewType.Help; - accessibleViewHelpProvider.provideContent = () => this._getAccessibleViewHelpDialogContent(accessibleViewHelpProvider!); + accessibleViewHelpProvider.provideContent = () => this._getAccessibleViewHelpDialogContent(accessibleViewHelpProvider); accessibleViewHelpProvider.onClose = () => this.show(previousProvider); - this._accessiblityHelpIsShown.set(true); - const delegate: IContextViewDelegate = { - getAnchor: () => { return { x: (window.innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; }, - render: (container) => { - container.classList.add('accessible-view-container'); - return this._render(accessibleViewHelpProvider, container, true); - }, - }; - this._contextViewService.showContextView(delegate); + accessibleViewHelpProvider.options.ariaLabel = localize('accessibleViewHelp', "Accessible View Help"); + this._contextViewService.hideContextView(); + setTimeout(() => { + this._accessiblityHelpIsShown.set(true); + const delegate: IContextViewDelegate = { + getAnchor: () => { return { x: (window.innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; }, + render: (container) => { + container.classList.add('accessible-view-container'); + return this._render(accessibleViewHelpProvider, container, true); + }, + }; + this._contextViewService.showContextView(delegate); + }, 100); } private _getAccessibleViewHelpDialogContent(provider: IAccessibleContentProvider): string { @@ -407,7 +411,7 @@ class AccessibleView extends Disposable { let goToSymbolHint = ''; if (hasSymbolProvider) { if (goToSymbolKb) { - goToSymbolHint = localize('goToSymbolHint', 'Go to a symbol within the accessible view (({0}).', goToSymbolKb); + goToSymbolHint = localize('goToSymbolHint', 'Go to a symbol within the accessible view ({0}).', goToSymbolKb); } else { goToSymbolHint = localize('goToSymbolHintNoKb', 'To go to a symbol within the accessible view, configure a keybinding for the command Go To Symbol in Accessible View.', goToSymbolKb); } From eb32c77b1498afb1920d4741435b8fa85b119436 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Aug 2023 09:57:15 -0700 Subject: [PATCH 14/69] fix issue --- .../contrib/accessibility/browser/accessibleView.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 047c4ee89ab..1db9068e730 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -314,6 +314,9 @@ class AccessibleView extends Disposable { } else { ariaLabel = provider.options.ariaLabel ? provider.options.ariaLabel : localize('helpAriaNoKb', "Accessible View"); } + if (internal) { + ariaLabel = localize('accessibleViewHelp', "Accessible View Help"); + } this._editorWidget.updateOptions({ ariaLabel }); this._editorWidget.focus(); }); @@ -367,7 +370,6 @@ class AccessibleView extends Disposable { accessibleViewHelpProvider.options.type = AccessibleViewType.Help; accessibleViewHelpProvider.provideContent = () => this._getAccessibleViewHelpDialogContent(accessibleViewHelpProvider); accessibleViewHelpProvider.onClose = () => this.show(previousProvider); - accessibleViewHelpProvider.options.ariaLabel = localize('accessibleViewHelp', "Accessible View Help"); this._contextViewService.hideContextView(); setTimeout(() => { this._accessiblityHelpIsShown.set(true); @@ -376,7 +378,7 @@ class AccessibleView extends Disposable { render: (container) => { container.classList.add('accessible-view-container'); return this._render(accessibleViewHelpProvider, container, true); - }, + } }; this._contextViewService.showContextView(delegate); }, 100); From 000f9f48ff8e282ee6db3616974d4c76b1a5bf9b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Aug 2023 11:29:21 -0700 Subject: [PATCH 15/69] fix syntax --- .../accessibility/browser/accessibleView.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 1db9068e730..6f6a2290475 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -288,7 +288,7 @@ class AccessibleView extends Disposable { } } - const fragment = message + provider.provideContent() + readMoreLink + disableHelpHint + localize('exit-tip', 'Exit this dialog via the Escape key.'); + const fragment = message + provider.provideContent() + readMoreLink + disableHelpHint + localize('exit-tip', '\nExit this dialog via the Escape key.'); this._getTextModel(URI.from({ path: `accessible-view-${provider.verbositySettingKey}`, scheme: 'accessible-view', fragment })).then((model) => { if (!model) { @@ -387,7 +387,14 @@ class AccessibleView extends Disposable { private _getAccessibleViewHelpDialogContent(provider: IAccessibleContentProvider): string { const navigationHint = this._getNavigationAriaHint(provider.verbositySettingKey); const goToSymbolHint = this._getGoToSymbolHint(provider); - return [navigationHint, goToSymbolHint].filter(i => !!i).join('\n'); + let hint = ''; + if (navigationHint) { + hint = navigationHint + '\n'; + } + if (goToSymbolHint) { + hint += goToSymbolHint + '\n'; + } + return hint; } private _getNavigationAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string { @@ -395,7 +402,7 @@ class AccessibleView extends Disposable { const nextKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleViewNext')?.getAriaLabel(); const previousKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleViewPrevious')?.getAriaLabel(); if (this._configurationService.getValue(verbositySettingKey)) { - hint = (nextKeybinding && previousKeybinding) ? localize('accessibleViewNextPreviousHint', "Show the next ({0}) or previous ({1}) item in the accessible view", nextKeybinding, previousKeybinding) : localize('chatAccessibleViewNextPreviousHintNoKb', "Show the next or previous item in the accessible view by configuring keybindings for Show Next / Previous in Accessible View"); + hint = (nextKeybinding && previousKeybinding) ? localize('accessibleViewNextPreviousHint', "Show the next ({0}) or previous ({1}) item in the accessible view.", nextKeybinding, previousKeybinding) : localize('chatAccessibleViewNextPreviousHintNoKb', "Show the next or previous item in the accessible view by configuring keybindings for Show Next / Previous in Accessible View."); } return hint; } @@ -415,7 +422,7 @@ class AccessibleView extends Disposable { if (goToSymbolKb) { goToSymbolHint = localize('goToSymbolHint', 'Go to a symbol within the accessible view ({0}).', goToSymbolKb); } else { - goToSymbolHint = localize('goToSymbolHintNoKb', 'To go to a symbol within the accessible view, configure a keybinding for the command Go To Symbol in Accessible View.', goToSymbolKb); + goToSymbolHint = localize('goToSymbolHintNoKb', 'To go to a symbol within the accessible view, configure a keybinding for the command Go To Symbol in Accessible View.'); } } return goToSymbolHint; From 29a451bcda52927698afb3b9e7835e808b68f85d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Aug 2023 11:31:58 -0700 Subject: [PATCH 16/69] make it easier to read --- .../accessibility/browser/accessibleView.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 6f6a2290475..6c4f5ccbdc2 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -401,8 +401,10 @@ class AccessibleView extends Disposable { let hint = ''; const nextKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleViewNext')?.getAriaLabel(); const previousKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleViewPrevious')?.getAriaLabel(); - if (this._configurationService.getValue(verbositySettingKey)) { - hint = (nextKeybinding && previousKeybinding) ? localize('accessibleViewNextPreviousHint', "Show the next ({0}) or previous ({1}) item in the accessible view.", nextKeybinding, previousKeybinding) : localize('chatAccessibleViewNextPreviousHintNoKb', "Show the next or previous item in the accessible view by configuring keybindings for Show Next / Previous in Accessible View."); + if (this._configurationService.getValue(verbositySettingKey) && nextKeybinding && previousKeybinding) { + hint = localize('accessibleViewNextPreviousHint', "Show the next ({0}) or previous ({1}) item in the accessible view.", nextKeybinding, previousKeybinding); + } else { + localize('chatAccessibleViewNextPreviousHintNoKb', "Show the next or previous item in the accessible view by configuring keybindings for Show Next / Previous in Accessible View."); } return hint; } @@ -410,8 +412,14 @@ class AccessibleView extends Disposable { if (!this._configurationService.getValue(verbositySettingKey)) { return ''; } + let hint = ''; const disableKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleViewDisableHint', this._contextKeyService)?.getAriaLabel(); - return !!disableKeybinding ? localize('acessibleViewDisableHint', "Disable the hint to open the accessible view by pressing ({0}).", disableKeybinding) : localize('accessibleViewDisableHintNoKb', 'Add a keybinding for the command Disable Accessible View Hint to disable this hint."'); + if (disableKeybinding) { + hint = localize('acessibleViewDisableHint', "Disable the hint to open the accessible view by pressing ({0}).", disableKeybinding); + } else { + hint = localize('accessibleViewDisableHintNoKb', "Add a keybinding for the command Disable Accessible View Hint to disable this hint."); + } + return hint; } private _getGoToSymbolHint(provider: IAccessibleContentProvider): string { From f5eb72539ef54e9e1625ddde90bd961d6d14b88d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Aug 2023 11:34:58 -0700 Subject: [PATCH 17/69] reorganize --- .../accessibility/browser/accessibleView.ts | 183 +++++++++--------- 1 file changed, 92 insertions(+), 91 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 6c4f5ccbdc2..e5647cc4ea0 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -4,9 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { alert } from 'vs/base/browser/ui/aria/aria'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { isMacintosh } from 'vs/base/common/platform'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { marked } from 'vs/base/common/marked/marked'; +import { isMacintosh } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { Command, EditorExtensionsRegistry, MultiCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; @@ -14,48 +16,22 @@ import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/wi import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { AccessibilityHelpNLS } from 'vs/editor/common/standaloneStrings'; +import { CodeActionController } from 'vs/editor/contrib/codeAction/browser/codeActionController'; import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewDelegate, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { alert } from 'vs/base/browser/ui/aria/aria'; -import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; -import { CodeActionController } from 'vs/editor/contrib/codeAction/browser/codeActionController'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { AccessibilityVerbositySettingId, accessibilityHelpIsShown, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; -import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; -import { marked } from 'vs/base/common/marked/marked'; - - -class AccessibleViewDisableHintAction extends Action2 { - constructor() { - super({ - id: 'editor.action.accessibleViewDisableHint', - keybinding: { - when: ContextKeyExpr.or(accessibleViewIsShown, accessibilityHelpIsShown), - primary: KeyMod.Alt | KeyCode.F6, - weight: KeybindingWeight.WorkbenchContrib - }, - menu: [{ - id: MenuId.CommandPalette, - group: '', - order: 1 - }], - title: localize('editor.action.accessibleViewDisableHint', "Disable Accessible View Hint") - }); - } - run(accessor: ServicesAccessor): void { - accessor.get(IAccessibleViewService).disableHint(); - } -} -registerAction2(AccessibleViewDisableHintAction); +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { AccessibilityVerbositySettingId, accessibilityHelpIsShown, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; const enum DIMENSIONS { MAX_WIDTH = 600 @@ -303,7 +279,7 @@ class AccessibleView extends Disposable { container.appendChild(this._editorContainer); let ariaLabel = ''; let helpHint = ''; - if (this._configurationService.getValue(provider.verbositySettingKey) && provider.options.type !== 'help') { + if (this._configurationService.getValue(provider.verbositySettingKey) && provider.options.type !== AccessibleViewType.Help) { const accessibilityHelpKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibilityHelp')?.getLabel(); if (accessibilityHelpKeybinding) { helpHint = localize('ariaAccessibilityHelp', "Use {0} for accessibility help", accessibilityHelpKeybinding); @@ -486,6 +462,44 @@ export class AccessibleViewService extends Disposable implements IAccessibleView } } + +class AccessibleViewSymbolQuickPick { + constructor(private _accessibleView: AccessibleView, @IQuickInputService private readonly _quickInputService: IQuickInputService) { + + } + show(provider: IAccessibleContentProvider): void { + const quickPick = this._quickInputService.createQuickPick(); + const picks = []; + const symbols = this._accessibleView.getSymbols(); + if (!symbols) { + return; + } + for (const symbol of symbols) { + picks.push({ + label: symbol.label, + ariaLabel: symbol.ariaLabel + }); + } + quickPick.canSelectMany = false; + quickPick.items = symbols; + quickPick.show(); + quickPick.onDidAccept(() => { + this._accessibleView.showSymbol(provider, quickPick.selectedItems[0]); + quickPick.hide(); + }); + quickPick.onDidHide(() => { + if (quickPick.selectedItems.length === 0) { + // this was escaped, so refocus the accessible view + this._accessibleView.show(provider); + } + }); + } +} + +interface IAccessibleViewSymbol extends IPickerQuickAccessItem { + info: string; +} + class AccessibleViewNextAction extends Action2 { constructor() { super({ @@ -510,29 +524,6 @@ class AccessibleViewNextAction extends Action2 { registerAction2(AccessibleViewNextAction); -class AccessibleViewGoToSymbolAction extends Action2 { - constructor() { - super({ - id: 'editor.action.accessibleViewGoToSymbol', - precondition: accessibleViewIsShown, - keybinding: { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO, - weight: KeybindingWeight.WorkbenchContrib + 10 - }, - menu: [{ - id: MenuId.CommandPalette, - group: '', - order: 1 - }], - title: localize('editor.action.accessibleViewGoToSymbol', "Go To Symbol in Accessible View") - }); - } - run(accessor: ServicesAccessor): void { - accessor.get(IAccessibleViewService).goToSymbol(); - } -} -registerAction2(AccessibleViewGoToSymbolAction); - class AccessibleViewPreviousAction extends Action2 { constructor() { super({ @@ -556,6 +547,30 @@ class AccessibleViewPreviousAction extends Action2 { } registerAction2(AccessibleViewPreviousAction); + +class AccessibleViewGoToSymbolAction extends Action2 { + constructor() { + super({ + id: 'editor.action.accessibleViewGoToSymbol', + precondition: accessibleViewIsShown, + keybinding: { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO, + weight: KeybindingWeight.WorkbenchContrib + 10 + }, + menu: [{ + id: MenuId.CommandPalette, + group: '', + order: 1 + }], + title: localize('editor.action.accessibleViewGoToSymbol', "Go To Symbol in Accessible View") + }); + } + run(accessor: ServicesAccessor): void { + accessor.get(IAccessibleViewService).goToSymbol(); + } +} +registerAction2(AccessibleViewGoToSymbolAction); + function registerCommand(command: T): T { command.register(); return command; @@ -600,40 +615,26 @@ export const AccessibleViewAction = registerCommand(new MultiCommand({ }], })); - -class AccessibleViewSymbolQuickPick { - constructor(private _accessibleView: AccessibleView, @IQuickInputService private readonly _quickInputService: IQuickInputService) { - - } - show(provider: IAccessibleContentProvider): void { - const quickPick = this._quickInputService.createQuickPick(); - const picks = []; - const symbols = this._accessibleView.getSymbols(); - if (!symbols) { - return; - } - for (const symbol of symbols) { - picks.push({ - label: symbol.label, - ariaLabel: symbol.ariaLabel - }); - } - quickPick.canSelectMany = false; - quickPick.items = symbols; - quickPick.show(); - quickPick.onDidAccept(() => { - this._accessibleView.showSymbol(provider, quickPick.selectedItems[0]); - quickPick.hide(); - }); - quickPick.onDidHide(() => { - if (quickPick.selectedItems.length === 0) { - // this was escaped, so refocus the accessible view - this._accessibleView.show(provider); - } +class AccessibleViewDisableHintAction extends Action2 { + constructor() { + super({ + id: 'editor.action.accessibleViewDisableHint', + keybinding: { + when: ContextKeyExpr.or(accessibleViewIsShown, accessibilityHelpIsShown), + primary: KeyMod.Alt | KeyCode.F6, + weight: KeybindingWeight.WorkbenchContrib + }, + menu: [{ + id: MenuId.CommandPalette, + group: '', + order: 1 + }], + title: localize('editor.action.accessibleViewDisableHint', "Disable Accessible View Hint") }); } + run(accessor: ServicesAccessor): void { + accessor.get(IAccessibleViewService).disableHint(); + } } +registerAction2(AccessibleViewDisableHintAction); -interface IAccessibleViewSymbol extends IPickerQuickAccessItem { - info: string; -} From f011814dfe1083ee92fa5cbe908d3d518de6e964 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 8 Aug 2023 13:44:47 -0700 Subject: [PATCH 18/69] Fix test compile --- .../test/browser/terminalInstance.test.ts | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts index 9fd40901d9e..295fb4bb119 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts @@ -197,8 +197,8 @@ suite('Workbench - TerminalInstance', () => { test('should resolve to "" when the template variables are empty', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' - ', title: '', description: '' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: '' }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: '' })); // TODO: // terminalLabelComputer.onLabelChanged(e => { // strictEqual(e.title, ''); @@ -210,80 +210,80 @@ suite('Workbench - TerminalInstance', () => { test('should resolve cwd', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' - ', title: '${cwd}', description: '${cwd}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, cwd: ROOT_1 }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, cwd: ROOT_1 })); strictEqual(terminalLabelComputer.title, ROOT_1); strictEqual(terminalLabelComputer.description, ROOT_1); }); test('should resolve workspaceFolder', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' - ', title: '${workspaceFolder}', description: '${workspaceFolder}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'zsh', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: 'folder' }) } as IWorkspaceFolder }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'zsh', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: 'folder' }) } as IWorkspaceFolder })); strictEqual(terminalLabelComputer.title, 'folder'); strictEqual(terminalLabelComputer.description, 'folder'); }); test('should resolve local', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' - ', title: '${local}', description: '${local}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'zsh', shellLaunchConfig: { type: 'Local' } }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'zsh', shellLaunchConfig: { type: 'Local' } })); strictEqual(terminalLabelComputer.title, 'Local'); strictEqual(terminalLabelComputer.description, 'Local'); }); test('should resolve process', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' - ', title: '${process}', description: '${process}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'zsh' }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'zsh' })); strictEqual(terminalLabelComputer.title, 'zsh'); strictEqual(terminalLabelComputer.description, 'zsh'); }); test('should resolve sequence', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' - ', title: '${sequence}', description: '${sequence}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, sequence: 'sequence' }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, sequence: 'sequence' })); strictEqual(terminalLabelComputer.title, 'sequence'); strictEqual(terminalLabelComputer.description, 'sequence'); }); test('should resolve task', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${process}${separator}${task}', description: '${task}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'zsh', shellLaunchConfig: { type: 'Task' } }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'zsh', shellLaunchConfig: { type: 'Task' } })); strictEqual(terminalLabelComputer.title, 'zsh ~ Task'); strictEqual(terminalLabelComputer.description, 'Task'); }); test('should resolve separator', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${separator}', description: '${separator}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'zsh', shellLaunchConfig: { type: 'Task' } }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'zsh', shellLaunchConfig: { type: 'Task' } })); strictEqual(terminalLabelComputer.title, 'zsh'); strictEqual(terminalLabelComputer.description, ''); }); test('should always return static title when specified', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${process}', description: '${workspaceFolder}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: 'folder' }) } as IWorkspaceFolder, staticTitle: 'my-title' }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: 'folder' }) } as IWorkspaceFolder, staticTitle: 'my-title' })); strictEqual(terminalLabelComputer.title, 'my-title'); strictEqual(terminalLabelComputer.description, 'folder'); }); test('should provide cwdFolder for all cwds only when in multi-root', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${process}${separator}${cwdFolder}', description: '${cwdFolder}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_1 }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_1 })); // single-root, cwd is same as root strictEqual(terminalLabelComputer.title, 'process'); strictEqual(terminalLabelComputer.description, ''); // multi-root configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${process}${separator}${cwdFolder}', description: '${cwdFolder}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_2 }), new TestFileService(), mockMultiRootContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockMultiRootContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_2 })); if (isWindows) { strictEqual(terminalLabelComputer.title, 'process'); strictEqual(terminalLabelComputer.description, ''); @@ -295,13 +295,13 @@ suite('Workbench - TerminalInstance', () => { test('should hide cwdFolder in single folder workspaces when cwd matches the workspace\'s default cwd even when slashes differ', async () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${process}${separator}${cwdFolder}', description: '${cwdFolder}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_1 }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_1 })); strictEqual(terminalLabelComputer.title, 'process'); strictEqual(terminalLabelComputer.description, ''); if (!isWindows) { - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_2 }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_2 })); strictEqual(terminalLabelComputer.title, 'process ~ root2'); strictEqual(terminalLabelComputer.description, 'root2'); } From 76a71731005febcbe4d803217ad2f20415b17d5a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Aug 2023 13:48:33 -0700 Subject: [PATCH 19/69] add toolbar and more --- src/vs/platform/actions/common/actions.ts | 1 + .../accessibility/browser/accessibleView.ts | 128 ++++++++++++++---- 2 files changed, 102 insertions(+), 27 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 3139a073b5d..cca29053fd0 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -186,6 +186,7 @@ export class MenuId { static readonly ChatCodeBlock = new MenuId('ChatCodeblock'); static readonly ChatMessageTitle = new MenuId('ChatMessageTitle'); static readonly ChatExecute = new MenuId('ChatExecute'); + static readonly AccessibleView = new MenuId('AccessibleView'); /** * Create or reuse a `MenuId` with the given identifier diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index e5647cc4ea0..af4992fc706 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -5,6 +5,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { alert } from 'vs/base/browser/ui/aria/aria'; +import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { marked } from 'vs/base/common/marked/marked'; @@ -19,6 +20,7 @@ import { AccessibilityHelpNLS } from 'vs/editor/common/standaloneStrings'; import { CodeActionController } from 'vs/editor/contrib/codeAction/browser/codeActionController'; import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -61,6 +63,7 @@ export interface IAccessibleViewService { previous(): void; goToSymbol(): void; disableHint(): void; + focusToolbar(): void; /** * If the setting is enabled, provides the open accessible view hint as a localized string. * @param verbositySettingKey The setting key for the verbosity of the feature @@ -90,6 +93,7 @@ class AccessibleView extends Disposable { get editorWidget() { return this._editorWidget; } private _editorContainer: HTMLElement; private _currentProvider: IAccessibleContentProvider | undefined; + private readonly _toolbar: MenuWorkbenchToolBar; constructor( @IOpenerService private readonly _openerService: IOpenerService, @@ -110,6 +114,7 @@ class AccessibleView extends Disposable { const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { contributions: EditorExtensionsRegistry.getEditorContributions().filter(c => c.id !== CodeActionController.ID) }; + this._toolbar = this._register(_instantiationService.createInstance(MenuWorkbenchToolBar, this._editorContainer, MenuId.AccessibleView, { ariaLabel: localize('accessibleViewToolbar', "Accessible View Toolbar") })); const editorOptions: IEditorConstructionOptions = { ...getSimpleEditorOptions(this._configurationService), lineDecorationsWidth: 6, @@ -185,7 +190,10 @@ class AccessibleView extends Disposable { } goToSymbol(): void { - this._instantiationService.createInstance(AccessibleViewSymbolQuickPick, this).show(this._currentProvider!); + if (!this._currentProvider) { + return; + } + this._instantiationService.createInstance(AccessibleViewSymbolQuickPick, this).show(this._currentProvider); } getSymbols(): IAccessibleViewSymbol[] | undefined { @@ -264,7 +272,7 @@ class AccessibleView extends Disposable { } } - const fragment = message + provider.provideContent() + readMoreLink + disableHelpHint + localize('exit-tip', '\nExit this dialog via the Escape key.'); + const fragment = message + provider.provideContent() + readMoreLink + disableHelpHint + localize('exit-tip', 'Exit this dialog via the Escape key.'); this._getTextModel(URI.from({ path: `accessible-view-${provider.verbositySettingKey}`, scheme: 'accessible-view', fragment })).then((model) => { if (!model) { @@ -279,16 +287,17 @@ class AccessibleView extends Disposable { container.appendChild(this._editorContainer); let ariaLabel = ''; let helpHint = ''; - if (this._configurationService.getValue(provider.verbositySettingKey) && provider.options.type !== AccessibleViewType.Help) { + const verbose = this._configurationService.getValue(provider.verbositySettingKey); + if (verbose && provider.options.type === AccessibleViewType.View) { const accessibilityHelpKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibilityHelp')?.getLabel(); if (accessibilityHelpKeybinding) { helpHint = localize('ariaAccessibilityHelp', "Use {0} for accessibility help", accessibilityHelpKeybinding); } - } - if (helpHint) { - ariaLabel = provider.options.ariaLabel ? localize('helpAriaKb', "{0}, {1}", provider.options.ariaLabel, helpHint) : localize('accessible-view', "Accessible View, {0}", helpHint); - } else { - ariaLabel = provider.options.ariaLabel ? provider.options.ariaLabel : localize('helpAriaNoKb', "Accessible View"); + if (helpHint) { + ariaLabel = provider.options.ariaLabel ? localize('helpAriaKb', "{0}, {1}", provider.options.ariaLabel, helpHint) : localize('accessible-view', "Accessible View, {0}", helpHint); + } else { + ariaLabel = provider.options.ariaLabel ? provider.options.ariaLabel : localize('helpAriaNoKb', "Accessible View"); + } } if (internal) { ariaLabel = localize('accessibleViewHelp', "Accessible View Help"); @@ -296,6 +305,8 @@ class AccessibleView extends Disposable { this._editorWidget.updateOptions({ ariaLabel }); this._editorWidget.focus(); }); + this._toolbar.context = { viewId: 'accessibleView' }; + this._toolbar.getElement().tabIndex = 0; const disposableStore = new DisposableStore(); disposableStore.add(this._editorWidget.onKeyUp((e) => { provider.onKeyDown?.(e); @@ -314,12 +325,20 @@ class AccessibleView extends Disposable { e.stopPropagation(); } })); - disposableStore.add(this._editorWidget.onDidBlurEditorText(() => this._contextViewService.hideContextView())); + this._editorWidget.onDidBlurEditorWidget(() => { + if (document.activeElement !== this._toolbar.getElement()) { + this._contextViewService.hideContextView(); + } + }); disposableStore.add(this._editorWidget.onDidContentSizeChange(() => this._layout())); disposableStore.add(this._layoutService.onDidLayout(() => this._layout())); return disposableStore; } + focusToolbar(): void { + this._toolbar.focus(); + } + private _layout(): void { const dimension = this._layoutService.dimension; const maxHeight = dimension.height && dimension.height * .4; @@ -348,7 +367,6 @@ class AccessibleView extends Disposable { accessibleViewHelpProvider.onClose = () => this.show(previousProvider); this._contextViewService.hideContextView(); setTimeout(() => { - this._accessiblityHelpIsShown.set(true); const delegate: IContextViewDelegate = { getAnchor: () => { return { x: (window.innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; }, render: (container) => { @@ -361,26 +379,41 @@ class AccessibleView extends Disposable { } private _getAccessibleViewHelpDialogContent(provider: IAccessibleContentProvider): string { - const navigationHint = this._getNavigationAriaHint(provider.verbositySettingKey); + const navigationHint = this._getNavigationHint(); const goToSymbolHint = this._getGoToSymbolHint(provider); - let hint = ''; + const toolbarHint = this._getToolbarHint(); + let hint = localize('intro', "In the accessible view, you can:\n"); if (navigationHint) { - hint = navigationHint + '\n'; + hint += ' - ' + navigationHint + '\n'; } if (goToSymbolHint) { - hint += goToSymbolHint + '\n'; + hint += ' - ' + goToSymbolHint + '\n'; + } + if (toolbarHint) { + hint += ' - ' + toolbarHint + '\n'; } return hint; } - private _getNavigationAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string { + private _getToolbarHint(): string { + let toolbarHint = ''; + const toolbarKb = this._keybindingService.lookupKeybinding('editor.action.accessibleViewFocusToolbar')?.getLabel(); + if (toolbarKb) { + toolbarHint = localize('toolbar', "Navigate to the toolbar ({0} or Shift+Tab)", toolbarKb); + } else { + toolbarHint = localize('toolbarNoKb', "Navigate to the toolbar (Shift+Tab)"); + } + return toolbarHint; + } + + private _getNavigationHint(): string { let hint = ''; const nextKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleViewNext')?.getAriaLabel(); const previousKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleViewPrevious')?.getAriaLabel(); - if (this._configurationService.getValue(verbositySettingKey) && nextKeybinding && previousKeybinding) { - hint = localize('accessibleViewNextPreviousHint', "Show the next ({0}) or previous ({1}) item in the accessible view.", nextKeybinding, previousKeybinding); + if (nextKeybinding && previousKeybinding) { + hint = localize('accessibleViewNextPreviousHint', "Show the next ({0}) or previous ({1}) item", nextKeybinding, previousKeybinding); } else { - localize('chatAccessibleViewNextPreviousHintNoKb', "Show the next or previous item in the accessible view by configuring keybindings for Show Next / Previous in Accessible View."); + hint = localize('chatAccessibleViewNextPreviousHintNoKb', "Show the next or previous item by configuring keybindings for the Show Next & Previous in Accessible View commands"); } return hint; } @@ -391,9 +424,9 @@ class AccessibleView extends Disposable { let hint = ''; const disableKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleViewDisableHint', this._contextKeyService)?.getAriaLabel(); if (disableKeybinding) { - hint = localize('acessibleViewDisableHint', "Disable the hint to open the accessible view by pressing ({0}).", disableKeybinding); + hint = localize('acessibleViewDisableHint', "Disable the aria label hint to open this ({0})", disableKeybinding); } else { - hint = localize('accessibleViewDisableHintNoKb', "Add a keybinding for the command Disable Accessible View Hint to disable this hint."); + hint = localize('accessibleViewDisableHintNoKb', "Add a keybinding for the command Disable Accessible View Hint to disable this hint"); } return hint; } @@ -404,9 +437,9 @@ class AccessibleView extends Disposable { let goToSymbolHint = ''; if (hasSymbolProvider) { if (goToSymbolKb) { - goToSymbolHint = localize('goToSymbolHint', 'Go to a symbol within the accessible view ({0}).', goToSymbolKb); + goToSymbolHint = localize('goToSymbolHint', 'Go to a symbol ({0})', goToSymbolKb); } else { - goToSymbolHint = localize('goToSymbolHintNoKb', 'To go to a symbol within the accessible view, configure a keybinding for the command Go To Symbol in Accessible View.'); + goToSymbolHint = localize('goToSymbolHintNoKb', 'To go to a symbol, configure a keybinding for the command Go To Symbol in Accessible View'); } } return goToSymbolHint; @@ -460,6 +493,9 @@ export class AccessibleViewService extends Disposable implements IAccessibleView showAccessibleViewHelp(): void { this._accessibleView?.showAccessibleViewHelp(); } + focusToolbar(): void { + this._accessibleView?.focusToolbar(); + } } @@ -469,6 +505,7 @@ class AccessibleViewSymbolQuickPick { } show(provider: IAccessibleContentProvider): void { const quickPick = this._quickInputService.createQuickPick(); + quickPick.title = localize('accessibleViewSymbolQuickPickTitle', "Go to Symbol Accessible View"); const picks = []; const symbols = this._accessibleView.getSymbols(); if (!symbols) { @@ -504,7 +541,7 @@ class AccessibleViewNextAction extends Action2 { constructor() { super({ id: 'editor.action.accessibleViewNext', - precondition: accessibleViewIsShown, + precondition: ContextKeyExpr.and(accessibleViewIsShown, accessibilityHelpIsShown.negate()), keybinding: { primary: KeyMod.Alt | KeyCode.BracketRight, weight: KeybindingWeight.WorkbenchContrib @@ -513,7 +550,9 @@ class AccessibleViewNextAction extends Action2 { id: MenuId.CommandPalette, group: '', order: 1 - }], + }, + { id: MenuId.AccessibleView, group: 'navigation' }], + icon: Codicon.chevronRight, title: localize('editor.action.accessibleViewNext', "Show Next in Accessible View") }); } @@ -528,15 +567,19 @@ class AccessibleViewPreviousAction extends Action2 { constructor() { super({ id: 'editor.action.accessibleViewPrevious', - precondition: accessibleViewIsShown, + precondition: ContextKeyExpr.and(accessibleViewIsShown, accessibilityHelpIsShown.negate()), keybinding: { primary: KeyMod.Alt | KeyCode.BracketLeft, weight: KeybindingWeight.WorkbenchContrib }, + icon: Codicon.chevronLeft, menu: [{ id: MenuId.CommandPalette, group: '', - order: 1 + }, + { + id: MenuId.AccessibleView, + group: 'navigation' }], title: localize('editor.action.accessibleViewPrevious', "Show Previous in Accessible View") }); @@ -552,15 +595,19 @@ class AccessibleViewGoToSymbolAction extends Action2 { constructor() { super({ id: 'editor.action.accessibleViewGoToSymbol', - precondition: accessibleViewIsShown, + precondition: ContextKeyExpr.and(accessibleViewIsShown, accessibilityHelpIsShown.negate()), keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO, weight: KeybindingWeight.WorkbenchContrib + 10 }, + icon: Codicon.symbolField, menu: [{ id: MenuId.CommandPalette, group: '', order: 1 + }, { + id: MenuId.AccessibleView, + group: 'navigation' }], title: localize('editor.action.accessibleViewGoToSymbol', "Go To Symbol in Accessible View") }); @@ -624,10 +671,15 @@ class AccessibleViewDisableHintAction extends Action2 { primary: KeyMod.Alt | KeyCode.F6, weight: KeybindingWeight.WorkbenchContrib }, + icon: Codicon.treeFilterClear, menu: [{ id: MenuId.CommandPalette, group: '', order: 1 + }, + { + id: MenuId.AccessibleView, + group: 'navigation' }], title: localize('editor.action.accessibleViewDisableHint', "Disable Accessible View Hint") }); @@ -638,3 +690,25 @@ class AccessibleViewDisableHintAction extends Action2 { } registerAction2(AccessibleViewDisableHintAction); +class AccessibleViewFocusToolbarAction extends Action2 { + constructor() { + super({ + id: 'editor.action.accessibleViewFocusToolbar', + keybinding: { + when: accessibleViewIsShown, + primary: KeyMod.Alt | KeyCode.F7, + weight: KeybindingWeight.WorkbenchContrib + }, + menu: [{ + id: MenuId.CommandPalette, + group: '', + order: 1 + }], + title: localize('editor.action.accessibleViewFocusToolbar', "Accessible View Focus Toolbar") + }); + } + run(accessor: ServicesAccessor): void { + accessor.get(IAccessibleViewService).focusToolbar(); + } +} +registerAction2(AccessibleViewFocusToolbarAction); From 71282c3d5299e866a38168456629dce0fd1ee855 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 8 Aug 2023 14:00:03 -0700 Subject: [PATCH 20/69] forwarding: add built-in tunnel forwarding extension (#189874) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * forwarding: add built-in tunnel forwarding extension - Support public/private ports, which accounts for most of the work in the CLI. Previously ports were only privat. - Make the extension built-in. Ported from the remote-containers extension with some tweaks for privacy and durability. - This also removes the opt-in flag, by not reimplementing it 😛 Fixes https://github.com/microsoft/vscode/issues/189677 Fixes https://github.com/microsoft/vscode/issues/189678 * fixup! comments --------- Co-authored-by: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> --- build/gulpfile.extensions.js | 1 + build/npm/dirs.js | 1 + cli/src/commands/tunnels.rs | 6 +- cli/src/tunnels.rs | 2 +- cli/src/tunnels/dev_tunnels.rs | 39 ++- .../{forwarding.rs => local_forwarding.rs} | 95 ++++-- cli/src/tunnels/port_forwarder.rs | 4 +- cli/src/tunnels/protocol.rs | 17 +- .../tunnel-forwarding/.vscode/launch.json | 15 + extensions/tunnel-forwarding/.vscodeignore | 5 + .../extension.webpack.config.js | 20 ++ extensions/tunnel-forwarding/media/icon.png | Bin 0 -> 7191 bytes extensions/tunnel-forwarding/package.json | 59 ++++ extensions/tunnel-forwarding/package.nls.json | 7 + .../tunnel-forwarding/src/deferredPromise.ts | 62 ++++ extensions/tunnel-forwarding/src/extension.ts | 293 ++++++++++++++++++ extensions/tunnel-forwarding/src/split.ts | 49 +++ extensions/tunnel-forwarding/tsconfig.json | 16 + extensions/tunnel-forwarding/yarn.lock | 8 + 19 files changed, 664 insertions(+), 35 deletions(-) rename cli/src/tunnels/{forwarding.rs => local_forwarding.rs} (77%) create mode 100644 extensions/tunnel-forwarding/.vscode/launch.json create mode 100644 extensions/tunnel-forwarding/.vscodeignore create mode 100644 extensions/tunnel-forwarding/extension.webpack.config.js create mode 100644 extensions/tunnel-forwarding/media/icon.png create mode 100644 extensions/tunnel-forwarding/package.json create mode 100644 extensions/tunnel-forwarding/package.nls.json create mode 100644 extensions/tunnel-forwarding/src/deferredPromise.ts create mode 100644 extensions/tunnel-forwarding/src/extension.ts create mode 100644 extensions/tunnel-forwarding/src/split.ts create mode 100644 extensions/tunnel-forwarding/tsconfig.json create mode 100644 extensions/tunnel-forwarding/yarn.lock diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index e2c9e3d9aba..bcdb206606b 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -64,6 +64,7 @@ const compilations = [ 'search-result/tsconfig.json', 'references-view/tsconfig.json', 'simple-browser/tsconfig.json', + 'tunnel-forwarding/tsconfig.json', 'typescript-language-features/test-workspace/tsconfig.json', 'typescript-language-features/web/tsconfig.json', 'typescript-language-features/tsconfig.json', diff --git a/build/npm/dirs.js b/build/npm/dirs.js index 875390550da..faf3a6577a5 100644 --- a/build/npm/dirs.js +++ b/build/npm/dirs.js @@ -41,6 +41,7 @@ const dirs = [ 'extensions/references-view', 'extensions/search-result', 'extensions/simple-browser', + 'extensions/tunnel-forwarding', 'extensions/typescript-language-features', 'extensions/vscode-api-tests', 'extensions/vscode-colorize-tests', diff --git a/cli/src/commands/tunnels.rs b/cli/src/commands/tunnels.rs index 85bfecc50a5..c1b361fc81b 100644 --- a/cli/src/commands/tunnels.rs +++ b/cli/src/commands/tunnels.rs @@ -35,7 +35,7 @@ use crate::{ code_server::CodeServerArgs, create_service_manager, dev_tunnels::{self, DevTunnels}, - forwarding, legal, + local_forwarding, legal, paths::get_all_servers, protocol, serve_stream, shutdown_signal::ShutdownRequest, @@ -444,7 +444,7 @@ pub async fn forward( match acquire_singleton(&ctx.paths.forwarding_lockfile()).await { Ok(SingletonConnection::Client(stream)) => { debug!(ctx.log, "starting as client to singleton"); - let r = forwarding::client(forwarding::SingletonClientArgs { + let r = local_forwarding::client(local_forwarding::SingletonClientArgs { log: ctx.log.clone(), shutdown: shutdown.clone(), stream, @@ -477,7 +477,7 @@ pub async fn forward( .start_new_launcher_tunnel(None, true, &forward_args.ports) .await?; - forwarding::server(ctx.log, tunnel, server, own_ports_rx, shutdown).await?; + local_forwarding::server(ctx.log, tunnel, server, own_ports_rx, shutdown).await?; Ok(0) } diff --git a/cli/src/tunnels.rs b/cli/src/tunnels.rs index 700516abb1f..7378cf34afd 100644 --- a/cli/src/tunnels.rs +++ b/cli/src/tunnels.rs @@ -11,7 +11,7 @@ pub mod protocol; pub mod shutdown_signal; pub mod singleton_client; pub mod singleton_server; -pub mod forwarding; +pub mod local_forwarding; mod wsl_detect; mod challenge; diff --git a/cli/src/tunnels/dev_tunnels.rs b/cli/src/tunnels/dev_tunnels.rs index 3bdc5cf189b..12ca6b4098e 100644 --- a/cli/src/tunnels/dev_tunnels.rs +++ b/cli/src/tunnels/dev_tunnels.rs @@ -23,13 +23,15 @@ use std::time::Duration; use tokio::sync::{mpsc, watch}; use tunnels::connections::{ForwardedPortConnection, RelayTunnelHost}; use tunnels::contracts::{ - Tunnel, TunnelPort, TunnelRelayTunnelEndpoint, PORT_TOKEN, TUNNEL_PROTOCOL_AUTO, + Tunnel, TunnelAccessControl, TunnelPort, TunnelRelayTunnelEndpoint, PORT_TOKEN, + TUNNEL_ACCESS_SCOPES_CONNECT, TUNNEL_PROTOCOL_AUTO, }; use tunnels::management::{ new_tunnel_management, HttpError, TunnelLocator, TunnelManagementClient, TunnelRequestOptions, NO_REQUEST_OPTIONS, }; +use super::protocol::PortPrivacy; use super::wsl_detect::is_wsl_installed; static TUNNEL_COUNT_LIMIT_NAME: &str = "TunnelsPerUserPerLocation"; @@ -164,8 +166,12 @@ impl ActiveTunnel { } /// Forwards a port over TCP. - pub async fn add_port_tcp(&self, port_number: u16) -> Result<(), AnyError> { - self.manager.add_port_tcp(port_number).await?; + pub async fn add_port_tcp( + &self, + port_number: u16, + privacy: PortPrivacy, + ) -> Result<(), AnyError> { + self.manager.add_port_tcp(port_number, privacy).await?; Ok(()) } @@ -866,13 +872,18 @@ impl ActiveTunnelManager { /// Adds a port for TCP/IP forwarding. #[allow(dead_code)] // todo: port forwarding - pub async fn add_port_tcp(&self, port_number: u16) -> Result<(), WrappedError> { + pub async fn add_port_tcp( + &self, + port_number: u16, + privacy: PortPrivacy, + ) -> Result<(), WrappedError> { self.relay .lock() .await .add_port(&TunnelPort { port_number, protocol: Some(TUNNEL_PROTOCOL_AUTO.to_owned()), + access_control: Some(privacy_to_tunnel_acl(privacy)), ..Default::default() }) .await @@ -1081,6 +1092,26 @@ fn vec_eq_as_set(a: &[String], b: &[String]) -> bool { true } +fn privacy_to_tunnel_acl(privacy: PortPrivacy) -> TunnelAccessControl { + let mut acl = TunnelAccessControl { entries: vec![] }; + + if privacy == PortPrivacy::Public { + acl.entries + .push(tunnels::contracts::TunnelAccessControlEntry { + kind: tunnels::contracts::TunnelAccessControlEntryType::Anonymous, + provider: None, + is_inherited: false, + is_deny: false, + is_inverse: false, + organization: None, + subjects: vec![], + scopes: vec![TUNNEL_ACCESS_SCOPES_CONNECT.to_string()], + }); + } + + acl +} + #[cfg(test)] mod test { use super::*; diff --git a/cli/src/tunnels/forwarding.rs b/cli/src/tunnels/local_forwarding.rs similarity index 77% rename from cli/src/tunnels/forwarding.rs rename to cli/src/tunnels/local_forwarding.rs index 1557b97c04e..e6410860cb0 100644 --- a/cli/src/tunnels/forwarding.rs +++ b/cli/src/tunnels/local_forwarding.rs @@ -5,6 +5,7 @@ use std::{ collections::HashMap, + ops::{Index, IndexMut}, sync::{Arc, Mutex}, }; @@ -26,11 +27,52 @@ use super::{ protocol::{ self, forward_singleton::{PortList, SetPortsResponse}, + PortPrivacy, }, shutdown_signal::ShutdownSignal, }; -type PortMap = HashMap; +#[derive(Default, Clone)] +struct PortCount { + public: u32, + private: u32, +} + +impl Index for PortCount { + type Output = u32; + + fn index(&self, privacy: PortPrivacy) -> &Self::Output { + match privacy { + PortPrivacy::Public => &self.public, + PortPrivacy::Private => &self.private, + } + } +} + +impl IndexMut for PortCount { + fn index_mut(&mut self, privacy: PortPrivacy) -> &mut Self::Output { + match privacy { + PortPrivacy::Public => &mut self.public, + PortPrivacy::Private => &mut self.private, + } + } +} + +impl PortCount { + fn is_empty(&self) -> bool { + self.public == 0 && self.private == 0 + } + + fn primary_privacy(&self) -> PortPrivacy { + if self.public > 0 { + PortPrivacy::Public + } else { + PortPrivacy::Private + } + } +} + +type PortMap = HashMap; /// The PortForwardingHandle is given out to multiple consumers to allow /// them to set_ports that they want to be forwarded. @@ -56,23 +98,25 @@ impl PortForwardingSender { self.sender.lock().unwrap().send_modify(|v| { for p in current.iter() { if !ports.contains(p) { - match v.get(p) { - Some(1) => { - v.remove(p); - } - Some(n) => { - v.insert(*p, n - 1); - } - None => unreachable!("removed port not in map"), + let n = v.get_mut(&p.number).expect("expected port in map"); + n[p.privacy] -= 1; + if n.is_empty() { + v.remove(&p.number); } } } for p in ports.iter() { if !current.contains(p) { - match v.get(p) { - Some(n) => v.insert(*p, n + 1), - None => v.insert(*p, 1), + match v.get_mut(&p.number) { + Some(n) => { + n[p.privacy] += 1; + } + None => { + let mut pc = PortCount::default(); + pc[p.privacy] += 1; + v.insert(p.number, pc); + } }; } } @@ -116,23 +160,26 @@ impl PortForwardingReceiver { /// Applies all changes from PortForwardingHandles to the tunnel. pub async fn apply_to(&mut self, log: log::Logger, tunnel: Arc) { - let mut current = vec![]; + let mut current: PortMap = HashMap::new(); while self.receiver.changed().await.is_ok() { - let next = self.receiver.borrow().keys().copied().collect::>(); + let next = self.receiver.borrow().clone(); - for p in current.iter() { - if !next.contains(p) { - match tunnel.remove_port(*p).await { - Ok(_) => info!(log, "stopped forwarding port {}", p), - Err(e) => error!(log, "failed to stop forwarding port {}: {}", p, e), + for (port, count) in current.iter() { + let privacy = count.primary_privacy(); + if !matches!(next.get(port), Some(n) if n.primary_privacy() == privacy) { + match tunnel.remove_port(*port).await { + Ok(_) => info!(log, "stopped forwarding port {} at {:?}", *port, privacy), + Err(e) => error!(log, "failed to stop forwarding port {}: {}", port, e), } } } - for p in next.iter() { - if !current.contains(p) { - match tunnel.add_port_tcp(*p).await { - Ok(_) => info!(log, "forwarding port {}", p), - Err(e) => error!(log, "failed to forward port {}: {}", p, e), + + for (port, count) in next.iter() { + let privacy = count.primary_privacy(); + if !matches!(current.get(port), Some(n) if n.primary_privacy() == privacy) { + match tunnel.add_port_tcp(*port, privacy).await { + Ok(_) => info!(log, "forwarding port {} at {:?}", port, privacy), + Err(e) => error!(log, "failed to forward port {}: {}", port, e), } } } diff --git a/cli/src/tunnels/port_forwarder.rs b/cli/src/tunnels/port_forwarder.rs index 0f489f541d7..093c1c8176f 100644 --- a/cli/src/tunnels/port_forwarder.rs +++ b/cli/src/tunnels/port_forwarder.rs @@ -12,7 +12,7 @@ use crate::{ util::errors::{AnyError, CannotForwardControlPort, ServerHasClosed}, }; -use super::dev_tunnels::ActiveTunnel; +use super::{dev_tunnels::ActiveTunnel, protocol::PortPrivacy}; pub enum PortForwardingRec { Forward(u16, oneshot::Sender>), @@ -87,7 +87,7 @@ impl PortForwardingProcessor { } if !self.forwarded.contains(&port) { - tunnel.add_port_tcp(port).await?; + tunnel.add_port_tcp(port, PortPrivacy::Private).await?; self.forwarded.insert(port); } diff --git a/cli/src/tunnels/protocol.rs b/cli/src/tunnels/protocol.rs index cf01917ee1e..89c4e384229 100644 --- a/cli/src/tunnels/protocol.rs +++ b/cli/src/tunnels/protocol.rs @@ -214,12 +214,27 @@ pub struct ChallengeVerifyParams { pub response: String, } +#[derive(Serialize, Deserialize, PartialEq, Eq, Copy, Clone, Debug)] +#[serde(rename_all = "lowercase")] +pub enum PortPrivacy { + Public, + Private, +} + pub mod forward_singleton { use serde::{Deserialize, Serialize}; + use super::PortPrivacy; + pub const METHOD_SET_PORTS: &str = "set_ports"; - pub type PortList = Vec; + #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] + pub struct PortRec { + pub number: u16, + pub privacy: PortPrivacy, + } + + pub type PortList = Vec; #[derive(Serialize, Deserialize)] pub struct SetPortsParams { diff --git a/extensions/tunnel-forwarding/.vscode/launch.json b/extensions/tunnel-forwarding/.vscode/launch.json new file mode 100644 index 00000000000..d3fabaa1a94 --- /dev/null +++ b/extensions/tunnel-forwarding/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "env": { "VSCODE_FORWARDING_IS_DEV": "1" } // load the CLI from OSS + } + ] +} diff --git a/extensions/tunnel-forwarding/.vscodeignore b/extensions/tunnel-forwarding/.vscodeignore new file mode 100644 index 00000000000..36e8b0714fa --- /dev/null +++ b/extensions/tunnel-forwarding/.vscodeignore @@ -0,0 +1,5 @@ +src/** +tsconfig.json +out/** +extension.webpack.config.js +yarn.lock \ No newline at end of file diff --git a/extensions/tunnel-forwarding/extension.webpack.config.js b/extensions/tunnel-forwarding/extension.webpack.config.js new file mode 100644 index 00000000000..b474e65cbb1 --- /dev/null +++ b/extensions/tunnel-forwarding/extension.webpack.config.js @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withDefaults = require('../shared.webpack.config'); + +module.exports = withDefaults({ + context: __dirname, + entry: { + extension: './src/extension.ts', + }, + resolve: { + mainFields: ['module', 'main'] + } +}); diff --git a/extensions/tunnel-forwarding/media/icon.png b/extensions/tunnel-forwarding/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2c90d30a1f054c51157a1bb4497e79e41d10eda4 GIT binary patch literal 7191 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 Mm#*rS=-|Wt2f~m<<^TWy literal 0 HcmV?d00001 diff --git a/extensions/tunnel-forwarding/package.json b/extensions/tunnel-forwarding/package.json new file mode 100644 index 00000000000..6bc5c446a02 --- /dev/null +++ b/extensions/tunnel-forwarding/package.json @@ -0,0 +1,59 @@ +{ + "name": "tunnel-forwarding", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.0.0", + "publisher": "vscode", + "license": "MIT", + "engines": { + "vscode": "^1.82.0" + }, + "icon": "media/icon.png", + "capabilities": { + "virtualWorkspaces": false, + "untrustedWorkspaces": { + "supported": true + } + }, + "enabledApiProposals": [ + "resolvers", + "tunnelFactory" + ], + "activationEvents": [ + "onStartupFinished" + ], + "contributes": { + "commands": [ + { + "category": "%category%", + "command": "tunnel-forwarding.showLog", + "title": "%command.showLog%", + "enablement": "tunnelForwardingHasLog" + }, + { + "category": "%category%", + "command": "tunnel-forwarding.restart", + "title": "%command.restart%", + "enablement": "tunnelForwardingIsRunning" + } + ] + }, + "main": "./out/extension", + "scripts": { + "compile": "gulp compile-extension:tunnel-forwarding", + "watch": "gulp watch-extension:tunnel-forwarding" + }, + "devDependencies": { + "@types/node": "18.x" + }, + "prettier": { + "printWidth": 100, + "trailingComma": "all", + "singleQuote": true, + "arrowParens": "avoid" + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + } +} diff --git a/extensions/tunnel-forwarding/package.nls.json b/extensions/tunnel-forwarding/package.nls.json new file mode 100644 index 00000000000..1102147e8ef --- /dev/null +++ b/extensions/tunnel-forwarding/package.nls.json @@ -0,0 +1,7 @@ +{ + "displayName": "Local Tunnel Port Forwarding", + "description": "Allows forwarding local ports to be accessible over the internet.", + "category": "Port Forwarding", + "command.showLog": "Show Log", + "command.restart": "Restart Forwarding System" +} diff --git a/extensions/tunnel-forwarding/src/deferredPromise.ts b/extensions/tunnel-forwarding/src/deferredPromise.ts new file mode 100644 index 00000000000..54b0737f3c0 --- /dev/null +++ b/extensions/tunnel-forwarding/src/deferredPromise.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export type ValueCallback = (value: T | Promise) => void; + +const enum DeferredOutcome { + Resolved, + Rejected +} + +/** + * Copied from src\vs\base\common\async.ts + */ +export class DeferredPromise { + + private completeCallback!: ValueCallback; + private errorCallback!: (err: unknown) => void; + private outcome?: { outcome: DeferredOutcome.Rejected; value: any } | { outcome: DeferredOutcome.Resolved; value: T }; + + public get isRejected() { + return this.outcome?.outcome === DeferredOutcome.Rejected; + } + + public get isResolved() { + return this.outcome?.outcome === DeferredOutcome.Resolved; + } + + public get isSettled() { + return !!this.outcome; + } + + public get value() { + return this.outcome?.outcome === DeferredOutcome.Resolved ? this.outcome?.value : undefined; + } + + public readonly p: Promise; + + constructor() { + this.p = new Promise((c, e) => { + this.completeCallback = c; + this.errorCallback = e; + }); + } + + public complete(value: T) { + return new Promise(resolve => { + this.completeCallback(value); + this.outcome = { outcome: DeferredOutcome.Resolved, value }; + resolve(); + }); + } + + public error(err: unknown) { + return new Promise(resolve => { + this.errorCallback(err); + this.outcome = { outcome: DeferredOutcome.Rejected, value: err }; + resolve(); + }); + } +} diff --git a/extensions/tunnel-forwarding/src/extension.ts b/extensions/tunnel-forwarding/src/extension.ts new file mode 100644 index 00000000000..f35d807ebe5 --- /dev/null +++ b/extensions/tunnel-forwarding/src/extension.ts @@ -0,0 +1,293 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { DeferredPromise } from './deferredPromise'; +import { splitNewLines } from './split'; + +export const enum TunnelPrivacyId { + Private = 'private', + Public = 'public', +} + +/** + * Timeout after the last port forwarding is disposed before we'll tear down + * the CLI. This is primarily used since privacy changes to port will appear + * as a dispose+re-create call, and we don't want to have to restart the CLI. + */ +const CLEANUP_TIMEOUT = 10_000; + +const cliPath = process.env.VSCODE_FORWARDING_IS_DEV + ? path.join(__dirname, '../../../cli/target/debug/code') + : path.join( + vscode.env.appRoot, + process.platform === 'win32' ? '../../bin' : 'bin', + vscode.env.appQuality === 'stable' ? 'code-tunnel' : 'code-tunnel-insiders', + ) + (process.platform === 'win32' ? '.exe' : ''); + +class Tunnel implements vscode.Tunnel { + private readonly disposeEmitter = new vscode.EventEmitter(); + public readonly onDidDispose = this.disposeEmitter.event; + public localAddress!: string; + + constructor( + public readonly remoteAddress: { port: number; host: string }, + public readonly privacy: TunnelPrivacyId, + ) { } + + public setPortFormat(formatString: string) { + this.localAddress = formatString.replace('{port}', String(this.remoteAddress.port)); + } + + dispose() { + this.disposeEmitter.fire(); + } +} + +const enum State { + Starting, + Active, + Inactive, + Error, +} + +type StateT = + | { state: State.Inactive } + | { state: State.Starting; process: ChildProcessWithoutNullStreams; cleanupTimeout?: NodeJS.Timeout } + | { state: State.Active; portFormat: string; process: ChildProcessWithoutNullStreams; cleanupTimeout?: NodeJS.Timeout } + | { state: State.Error; error: string }; + +export async function activate(context: vscode.ExtensionContext) { + if (vscode.env.remoteAuthority) { + return; // forwarding is local-only at the moment + } + + const logger = new Logger(vscode.l10n.t('Port Forwarding')); + const provider = new TunnelProvider(logger); + + context.subscriptions.push( + vscode.commands.registerCommand('tunnel-forwarding.showLog', () => logger.show()), + vscode.commands.registerCommand('tunnel-forwarding.restart', () => provider.restart()), + + provider.onDidStateChange(s => { + vscode.commands.executeCommand('setContext', 'tunnelForwardingIsRunning', s.state !== State.Inactive); + }), + + await vscode.workspace.registerTunnelProvider( + provider, + { + tunnelFeatures: { + elevation: false, + privacyOptions: [ + { themeIcon: 'globe', id: TunnelPrivacyId.Public, label: vscode.l10n.t('Public') }, + { themeIcon: 'lock', id: TunnelPrivacyId.Private, label: vscode.l10n.t('Private') }, + ], + }, + }, + ), + ); +} + +export function deactivate() { } + +class Logger { + private outputChannel?: vscode.LogOutputChannel; + + constructor(private readonly label: string) { } + + public show(): void { + return this.outputChannel?.show(); + } + + public clear() { + this.outputChannel?.clear(); + } + + public log( + logLevel: 'trace' | 'debug' | 'info' | 'warn' | 'error', + message: string, + ...args: unknown[] + ) { + if (!this.outputChannel) { + this.outputChannel = vscode.window.createOutputChannel(this.label, { log: true }); + vscode.commands.executeCommand('setContext', 'tunnelForwardingHasLog', true); + } + this.outputChannel[logLevel](message, ...args); + } +} + +class TunnelProvider implements vscode.TunnelProvider { + private readonly tunnels = new Set(); + private readonly stateChange = new vscode.EventEmitter(); + private _state: StateT = { state: State.Inactive }; + + private get state(): StateT { + return this._state; + } + + private set state(state: StateT) { + this._state = state; + this.stateChange.fire(state); + } + + public readonly onDidStateChange = this.stateChange.event; + + constructor(private readonly logger: Logger) { } + + /** @inheritdoc */ + public async provideTunnel(tunnelOptions: vscode.TunnelOptions): Promise { + const tunnel = new Tunnel( + tunnelOptions.remoteAddress, + (tunnelOptions.privacy as TunnelPrivacyId) || TunnelPrivacyId.Private, + ); + + this.tunnels.add(tunnel); + tunnel.onDidDispose(() => { + this.tunnels.delete(tunnel); + this.updateActivePortsIfRunning(); + }); + + switch (this.state.state) { + case State.Error: + case State.Inactive: + await this.setupPortForwardingProcess(); + // fall through since state is now starting + case State.Starting: + this.updateActivePortsIfRunning(); + return new Promise((resolve, reject) => { + const l = this.stateChange.event(state => { + if (state.state === State.Active) { + tunnel.setPortFormat(state.portFormat); + l.dispose(); + resolve(tunnel); + } else if (state.state === State.Error) { + l.dispose(); + reject(new Error(state.error)); + } + }); + }); + case State.Active: + tunnel.setPortFormat(this.state.portFormat); + this.updateActivePortsIfRunning(); + return tunnel; + } + } + + /** Re/starts the port forwarding system. */ + public async restart() { + this.killRunningProcess(); + await this.setupPortForwardingProcess(); // will show progress + this.updateActivePortsIfRunning(); + } + + private isInStateWithProcess(process: ChildProcessWithoutNullStreams) { + return ( + (this.state.state === State.Starting || this.state.state === State.Active) && + this.state.process === process + ); + } + + private killRunningProcess() { + if (this.state.state === State.Starting || this.state.state === State.Active) { + this.logger.log('info', '[forwarding] no more ports, stopping forwarding CLI'); + this.state.process.kill(); + this.state = { state: State.Inactive }; + } + } + + private updateActivePortsIfRunning() { + if (this.state.state !== State.Starting && this.state.state !== State.Active) { + return; + } + + const ports = [...this.tunnels].map(t => ({ number: t.remoteAddress.port, privacy: t.privacy })); + this.state.process.stdin.write(`${JSON.stringify(ports)}\n`); + + if (ports.length === 0 && !this.state.cleanupTimeout) { + this.state.cleanupTimeout = setTimeout(() => this.killRunningProcess(), CLEANUP_TIMEOUT); + } else if (ports.length > 0 && this.state.cleanupTimeout) { + clearTimeout(this.state.cleanupTimeout); + this.state.cleanupTimeout = undefined; + } + } + + private async setupPortForwardingProcess() { + const session = await vscode.authentication.getSession('github', ['user:email', 'read:org'], { + createIfNone: true, + }); + + const args = [ + '--verbose', + 'tunnel', + 'forward-internal', + '--provider', + 'github', + '--access-token', + session.accessToken, + ]; + + this.logger.log('info', '[forwarding] starting CLI'); + const process = spawn(cliPath, args, { stdio: 'pipe' }); + this.state = { state: State.Starting, process }; + + const progressP = new DeferredPromise(); + vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: vscode.l10n.t({ + comment: ['do not change link format [Show Log](command), only change the text "Show Log"'], + message: 'Starting port forwarding system ([Show Log]({0}))', + args: ['command:tunnel-forwarding.showLog'] + }), + }, + () => progressP.p, + ); + + let lastPortFormat: string | undefined; + process.on('exit', status => { + const msg = `[forwarding] exited with code ${status}`; + this.logger.log('info', msg); + progressP.complete(); // make sure to clear progress on unexpected exit + if (this.isInStateWithProcess(process)) { + this.state = { state: State.Error, error: msg }; + } + }); + + process.on('error', err => { + this.logger.log('error', `[forwarding] ${err}`); + progressP.complete(); // make sure to clear progress on unexpected exit + if (this.isInStateWithProcess(process)) { + this.state = { state: State.Error, error: String(err) }; + } + }); + + process.stdout + .pipe(splitNewLines()) + .on('data', line => this.logger.log('info', `[forwarding] ${line}`)) + .resume(); + + process.stderr + .pipe(splitNewLines()) + .on('data', line => { + try { + const l: { port_format: string } = JSON.parse(line); + if (l.port_format && l.port_format !== lastPortFormat) { + this.state = { state: State.Active, portFormat: l.port_format, process }; + progressP.complete(); + } + } catch (e) { + this.logger.log('error', `[forwarding] ${line}`); + } + }) + .resume(); + + await new Promise((resolve, reject) => { + process.on('spawn', resolve); + process.on('error', reject); + }); + } +} diff --git a/extensions/tunnel-forwarding/src/split.ts b/extensions/tunnel-forwarding/src/split.ts new file mode 100644 index 00000000000..6e9d7474604 --- /dev/null +++ b/extensions/tunnel-forwarding/src/split.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Transform } from 'stream'; + +export const splitNewLines = () => new StreamSplitter('\n'.charCodeAt(0)); + +/** + * Copied and simplified from src\vs\base\node\nodeStreams.ts + */ +export class StreamSplitter extends Transform { + private buffer: Buffer | undefined; + + constructor(private readonly splitter: number) { + super(); + } + + override _transform(chunk: Buffer, _encoding: string, callback: (error?: Error | null, data?: any) => void): void { + if (!this.buffer) { + this.buffer = chunk; + } else { + this.buffer = Buffer.concat([this.buffer, chunk]); + } + + let offset = 0; + while (offset < this.buffer.length) { + const index = this.buffer.indexOf(this.splitter, offset); + if (index === -1) { + break; + } + + this.push(this.buffer.subarray(offset, index + 1)); + offset = index + 1; + } + + this.buffer = offset === this.buffer.length ? undefined : this.buffer.subarray(offset); + callback(); + } + + override _flush(callback: (error?: Error | null, data?: any) => void): void { + if (this.buffer) { + this.push(this.buffer); + } + + callback(); + } +} diff --git a/extensions/tunnel-forwarding/tsconfig.json b/extensions/tunnel-forwarding/tsconfig.json new file mode 100644 index 00000000000..4769a9faec8 --- /dev/null +++ b/extensions/tunnel-forwarding/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out", + "downlevelIteration": true, + "types": [ + "node" + ] + }, + "include": [ + "src/**/*", + "../../src/vscode-dts/vscode.d.ts", + "../../src/vscode-dts/vscode.proposed.resolvers.d.ts", + "../../src/vscode-dts/vscode.proposed.tunnelFactory.d.ts" + ] +} diff --git a/extensions/tunnel-forwarding/yarn.lock b/extensions/tunnel-forwarding/yarn.lock new file mode 100644 index 00000000000..8a3d10f2b65 --- /dev/null +++ b/extensions/tunnel-forwarding/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/node@18.x": + version "18.15.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" + integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== From 3b9cb4263944cbe2d5339f83090deab62e8edcb5 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Tue, 8 Aug 2023 21:22:56 +0000 Subject: [PATCH 21/69] Modify environment collection scope API based on feedback --- .../api/common/extHostExtensionService.ts | 1 + .../api/common/extHostTerminalService.ts | 11 ++++---- ...scode.proposed.envCollectionWorkspace.d.ts | 25 ++++++++++++------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 0c35ea254f3..34e70b79107 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -532,6 +532,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme return that.extensionRuntime; }, get environmentVariableCollection() { return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription); }, + getEnvironmentVariableCollection(scope?: vscode.EnvironmentVariableScope) { return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription, scope); }, get messagePassingProtocol() { if (!messagePassingProtocol) { if (!messagePort) { diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 6566fff19de..1f95913ee42 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -51,9 +51,8 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID registerLinkProvider(provider: vscode.TerminalLinkProvider): vscode.Disposable; registerProfileProvider(extension: IExtensionDescription, id: string, provider: vscode.TerminalProfileProvider): vscode.Disposable; registerTerminalQuickFixProvider(id: string, extensionId: string, provider: vscode.TerminalQuickFixProvider): vscode.Disposable; - getEnvironmentVariableCollection(extension: IExtensionDescription): IEnvironmentVariableCollection; + getEnvironmentVariableCollection(extension: IExtensionDescription, scope?: vscode.EnvironmentVariableScope): vscode.EnvironmentVariableCollection; } -type IEnvironmentVariableCollection = vscode.EnvironmentVariableCollection & { getScopedEnvironmentVariableCollection(scope: vscode.EnvironmentVariableScope | undefined): vscode.EnvironmentVariableCollection }; export interface ITerminalInternalOptions { isFeatureTerminal?: boolean; useShellEnvironment?: boolean; @@ -833,13 +832,13 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I return index; } - public getEnvironmentVariableCollection(extension: IExtensionDescription): IEnvironmentVariableCollection { + public getEnvironmentVariableCollection(extension: IExtensionDescription, scope?: vscode.EnvironmentVariableScope): vscode.EnvironmentVariableCollection { let collection = this._environmentVariableCollections.get(extension.identifier.value); if (!collection) { collection = new UnifiedEnvironmentVariableCollection(extension); this._setEnvironmentVariableCollection(extension.identifier.value, collection); } - return collection.getScopedEnvironmentVariableCollection(undefined); + return collection.getScopedEnvironmentVariableCollection(scope); } private _syncEnvironmentVariableCollection(extensionIdentifier: string, collection: UnifiedEnvironmentVariableCollection): void { @@ -906,7 +905,7 @@ class UnifiedEnvironmentVariableCollection { this.map = new Map(serialized); } - getScopedEnvironmentVariableCollection(scope: vscode.EnvironmentVariableScope | undefined): IEnvironmentVariableCollection { + getScopedEnvironmentVariableCollection(scope: vscode.EnvironmentVariableScope | undefined): vscode.EnvironmentVariableCollection { if (this._extension && scope) { // TODO: This should be removed when the env var extension API(s) are stabilized checkProposedApiEnabled(this._extension, 'envCollectionWorkspace'); @@ -1049,7 +1048,7 @@ class UnifiedEnvironmentVariableCollection { } } -class ScopedEnvironmentVariableCollection implements vscode.EnvironmentVariableCollection, IEnvironmentVariableCollection { +class ScopedEnvironmentVariableCollection implements vscode.EnvironmentVariableCollection { public get persistent(): boolean { return this.collection.persistent; } public set persistent(value: boolean) { this.collection.persistent = value; diff --git a/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts b/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts index fff4ed4a337..a618e86a54e 100644 --- a/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts +++ b/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts @@ -7,15 +7,22 @@ declare module 'vscode' { // https://github.com/microsoft/vscode/issues/171173 - // export interface ExtensionContext { - // /** - // * Gets the extension's environment variable collection for this workspace, enabling changes - // * to be applied to terminal environment variables. - // * - // * @param scope The scope to which the environment variable collection applies to. - // */ - // readonly environmentVariableCollection: EnvironmentVariableCollection & { getScopedEnvironmentVariableCollection(scope: EnvironmentVariableScope): EnvironmentVariableCollection }; - // } + export interface ExtensionContext { + /** + * Gets the extension's environment variable collection for this workspace, enabling changes + * to be applied to terminal environment variables. + * + * @deprecated Use {@link getEnvironmentVariableCollection} instead. + */ + readonly environmentVariableCollection: EnvironmentVariableCollection; + /** + * Gets the extension's environment variable collection for this workspace, enabling changes + * to be applied to terminal environment variables. + * + * @param scope The scope to which the environment variable collection applies to. + */ + getEnvironmentVariableCollection(scope?: EnvironmentVariableScope): EnvironmentVariableCollection; + } export type EnvironmentVariableScope = { /** From 8f4385d10c0a5fc5161c71ae1572b238dc3a8e5a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 8 Aug 2023 14:26:50 -0700 Subject: [PATCH 22/69] Use unique ident pool per mangling (#189959) Fixes #189124 --- build/lib/mangle/index.js | 13 ++++--------- build/lib/mangle/index.ts | 12 ++++-------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/build/lib/mangle/index.js b/build/lib/mangle/index.js index 75981c79d06..27281954b74 100644 --- a/build/lib/mangle/index.js +++ b/build/lib/mangle/index.js @@ -246,12 +246,6 @@ function isNameTakenInFile(node, name) { } return false; } -const fileIdents = new class { - idents = new ShortIdent('$'); - next() { - return this.idents.next(); - } -}; const skippedExportMangledFiles = [ // Build 'css.build', @@ -301,7 +295,7 @@ class DeclarationData { node; service; replacementName; - constructor(fileName, node, service) { + constructor(fileName, node, service, fileIdents) { this.fileName = fileName; this.node = node; this.service = service; @@ -368,6 +362,7 @@ class Mangler { // STEP: // - Find all classes and their field info. // - Find exported symbols. + const fileIdents = new ShortIdent('$'); const visit = (node) => { if (this.config.manglePrivateFields) { if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) { @@ -410,7 +405,7 @@ class Mangler { if (isInAmbientContext(node)) { return; } - this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, this.service)); + this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, this.service, fileIdents)); } } ts.forEachChild(node, visit); @@ -662,4 +657,4 @@ async function _run() { if (__filename === process_1.argv[1]) { _run(); } -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyx5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLHFDQUErQjtBQUMvQiwyQ0FBeUQ7QUFDekQsaUNBQWlDO0FBQ2pDLDZCQUFvQztBQUNwQyx5Q0FBeUM7QUFDekMsMkVBQXdFO0FBQ3hFLE1BQU0sU0FBUyxHQUFHLE9BQU8sQ0FBQyx3QkFBd0IsQ0FBQyxDQUFDO0FBRXBELE1BQU0sVUFBVTtJQVlHO0lBVlYsTUFBTSxDQUFDLFNBQVMsR0FBRyxJQUFJLEdBQUcsQ0FBQyxDQUFDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxVQUFVO1FBQzlHLFNBQVMsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxJQUFJO1FBQ25HLFFBQVEsRUFBRSxJQUFJLEVBQUUsWUFBWSxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsT0FBTztRQUMxRyxNQUFNLEVBQUUsS0FBSyxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQztJQUU1RCxNQUFNLENBQUMsU0FBUyxHQUFHLGtFQUFrRSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUVoRyxNQUFNLEdBQUcsQ0FBQyxDQUFDO0lBRW5CLFlBQ2tCLE1BQWM7UUFBZCxXQUFNLEdBQU4sTUFBTSxDQUFRO0lBQzVCLENBQUM7SUFFTCxJQUFJLENBQUMsV0FBdUM7UUFDM0Ms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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyx5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLHFDQUErQjtBQUMvQiwyQ0FBeUQ7QUFDekQsaUNBQWlDO0FBQ2pDLDZCQUFvQztBQUNwQyx5Q0FBeUM7QUFDekMsMkVBQXdFO0FBQ3hFLE1BQU0sU0FBUyxHQUFHLE9BQU8sQ0FBQyx3QkFBd0IsQ0FBQyxDQUFDO0FBRXBELE1BQU0sVUFBVTtJQVlHO0lBVlYsTUFBTSxDQUFDLFNBQVMsR0FBRyxJQUFJLEdBQUcsQ0FBQyxDQUFDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxVQUFVO1FBQzlHLFNBQVMsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxJQUFJO1FBQ25HLFFBQVEsRUFBRSxJQUFJLEVBQUUsWUFBWSxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsT0FBTztRQUMxRyxNQUFNLEVBQUUsS0FBSyxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQztJQUU1RCxNQUFNLENBQUMsU0FBUyxHQUFHLGtFQUFrRSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUVoRyxNQUFNLEdBQUcsQ0FBQyxDQUFDO0lBRW5CLFlBQ2tCLE1BQWM7UUFBZCxXQUFNLEdBQU4sTUFBTSxDQUFRO0lBQzVCLENBQUM7SUFFTCxJQUFJLENBQUMsV0FBdUM7UUFDM0Ms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 \ No newline at end of file diff --git a/build/lib/mangle/index.ts b/build/lib/mangle/index.ts index a87fe97f456..035653c6f5b 100644 --- a/build/lib/mangle/index.ts +++ b/build/lib/mangle/index.ts @@ -278,13 +278,6 @@ function isNameTakenInFile(node: ts.Node, name: string): boolean { return false; } -const fileIdents = new class { - private readonly idents = new ShortIdent('$'); - - next() { - return this.idents.next(); - } -}; const skippedExportMangledFiles = [ // Build @@ -346,6 +339,7 @@ class DeclarationData { readonly fileName: string, readonly node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.EnumDeclaration | ts.VariableDeclaration, private readonly service: ts.LanguageService, + fileIdents: ShortIdent, ) { // Todo: generate replacement names based on usage count, with more used names getting shorter identifiers this.replacementName = fileIdents.next(); @@ -427,6 +421,8 @@ export class Mangler { // - Find all classes and their field info. // - Find exported symbols. + const fileIdents = new ShortIdent('$'); + const visit = (node: ts.Node): void => { if (this.config.manglePrivateFields) { if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) { @@ -475,7 +471,7 @@ export class Mangler { return; } - this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, this.service)); + this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, this.service, fileIdents)); } } From 700de16f251b92d0815a59e4dcda31b2e415d066 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Tue, 8 Aug 2023 21:31:50 +0000 Subject: [PATCH 23/69] Rename doc --- src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts b/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts index a618e86a54e..73bd11df4e2 100644 --- a/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts +++ b/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts @@ -16,7 +16,7 @@ declare module 'vscode' { */ readonly environmentVariableCollection: EnvironmentVariableCollection; /** - * Gets the extension's environment variable collection for this workspace, enabling changes + * Gets the extension's environment variable collection for this scope, enabling changes * to be applied to terminal environment variables. * * @param scope The scope to which the environment variable collection applies to. From 79c2fe497262945a4a1368a1e428c8dc8d72e676 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Aug 2023 14:37:24 -0700 Subject: [PATCH 24/69] add hack comments --- .../accessibility/browser/accessibleView.ts | 18 +++++++++--------- .../browser/accessibility/accessibility.css | 8 ++++++++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index af4992fc706..338183d84df 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -114,18 +115,18 @@ class AccessibleView extends Disposable { const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { contributions: EditorExtensionsRegistry.getEditorContributions().filter(c => c.id !== CodeActionController.ID) }; - this._toolbar = this._register(_instantiationService.createInstance(MenuWorkbenchToolBar, this._editorContainer, MenuId.AccessibleView, { ariaLabel: localize('accessibleViewToolbar', "Accessible View Toolbar") })); + this._toolbar = this._register(_instantiationService.createInstance(MenuWorkbenchToolBar, this._editorContainer, MenuId.AccessibleView, { ariaLabel: localize('accessibleViewToolbar', "Accessible View Toolbar"), orientation: ActionsOrientation.HORIZONTAL })); const editorOptions: IEditorConstructionOptions = { ...getSimpleEditorOptions(this._configurationService), lineDecorationsWidth: 6, - dragAndDrop: true, + dragAndDrop: false, cursorWidth: 1, wrappingStrategy: 'advanced', wrappingIndent: 'none', padding: { top: 2, bottom: 2 }, quickSuggestions: false, renderWhitespace: 'none', - dropIntoEditor: { enabled: true }, + dropIntoEditor: { enabled: false }, readOnly: true, fontFamily: 'var(--monaco-monospace-font)' }; @@ -308,14 +309,12 @@ class AccessibleView extends Disposable { this._toolbar.context = { viewId: 'accessibleView' }; this._toolbar.getElement().tabIndex = 0; const disposableStore = new DisposableStore(); - disposableStore.add(this._editorWidget.onKeyUp((e) => { - provider.onKeyDown?.(e); - })); + disposableStore.add(this._editorWidget.onKeyUp((e) => provider.onKeyDown?.(e))); disposableStore.add(this._editorWidget.onKeyDown((e) => { if (e.keyCode === KeyCode.Escape) { e.stopPropagation(); this._contextViewService.hideContextView(); - // Delay to allow the context view to hide #186514 + // HACK: Delay to allow the context view to hide #186514 setTimeout(() => provider.onClose(), 100); } else if (e.keyCode === KeyCode.KeyH && provider.options.readMoreUrl) { const url: string = provider.options.readMoreUrl!; @@ -325,11 +324,11 @@ class AccessibleView extends Disposable { e.stopPropagation(); } })); - this._editorWidget.onDidBlurEditorWidget(() => { + disposableStore.add(this._editorWidget.onDidBlurEditorWidget(() => { if (document.activeElement !== this._toolbar.getElement()) { this._contextViewService.hideContextView(); } - }); + })); disposableStore.add(this._editorWidget.onDidContentSizeChange(() => this._layout())); disposableStore.add(this._layoutService.onDidLayout(() => this._layout())); return disposableStore; @@ -366,6 +365,7 @@ class AccessibleView extends Disposable { accessibleViewHelpProvider.provideContent = () => this._getAccessibleViewHelpDialogContent(accessibleViewHelpProvider); accessibleViewHelpProvider.onClose = () => this.show(previousProvider); this._contextViewService.hideContextView(); + // HACK: Delay to allow the context view to hide #186514 setTimeout(() => { const delegate: IContextViewDelegate = { getAnchor: () => { return { x: (window.innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; }, diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css index 3a1665a903a..25fcb4b03d8 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css @@ -13,3 +13,11 @@ margin-top: -1px; z-index: 2550; } + +.accessible-view-container .actions-container { + display: flex; + margin: 0 auto; + padding: 0; + width: 100%; + justify-content: flex-end; +} From 8c7747252e33da679fb4b8922c56c4c235ad1b5d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 8 Aug 2023 14:44:03 -0700 Subject: [PATCH 25/69] Move drop/paste ids onto providers (#189961) For #179430, #30066 This lets us call just the provider we are interested in --- extensions/ipynb/src/notebookImagePaste.ts | 7 +++-- .../languageFeatures/copyFiles/copyPaste.ts | 9 +++--- .../copyFiles/copyPasteLinks.ts | 5 ++-- .../copyFiles/dropIntoEditor.ts | 7 ++--- .../singlefolder-tests/documentPaste.test.ts | 18 +++++------ src/vs/editor/common/languages.ts | 7 ++--- .../browser/copyPasteController.ts | 30 +++++++++---------- .../browser/defaultProviders.ts | 7 ++--- .../browser/dropIntoEditorController.ts | 12 ++++++-- .../contrib/dropOrPasteInto/browser/edit.ts | 4 +-- .../api/browser/mainThreadLanguageFeatures.ts | 26 +++++++++------- .../workbench/api/common/extHost.protocol.ts | 10 +++---- .../api/common/extHostLanguageFeatures.ts | 28 ++++++++--------- src/vs/workbench/api/common/extHostTypes.ts | 6 +--- .../vscode.proposed.documentPaste.d.ts | 19 +++++++----- .../vscode.proposed.dropMetadata.d.ts | 23 +++++++------- 16 files changed, 113 insertions(+), 105 deletions(-) diff --git a/extensions/ipynb/src/notebookImagePaste.ts b/extensions/ipynb/src/notebookImagePaste.ts index 900b0bada1f..94292c26a74 100644 --- a/extensions/ipynb/src/notebookImagePaste.ts +++ b/extensions/ipynb/src/notebookImagePaste.ts @@ -48,7 +48,7 @@ function getImageMimeType(uri: vscode.Uri): string | undefined { class DropOrPasteEditProvider implements vscode.DocumentPasteEditProvider, vscode.DocumentDropEditProvider { - private readonly id = 'insertAttachment'; + public readonly id = 'insertAttachment'; async provideDocumentPasteEdits( document: vscode.TextDocument, @@ -66,7 +66,7 @@ class DropOrPasteEditProvider implements vscode.DocumentPasteEditProvider, vscod return; } - const pasteEdit = new vscode.DocumentPasteEdit(insert.insertText, this.id, vscode.l10n.t('Insert Image as Attachment')); + const pasteEdit = new vscode.DocumentPasteEdit(insert.insertText, vscode.l10n.t('Insert Image as Attachment')); pasteEdit.yieldTo = [{ mimeType: MimeType.plain }]; pasteEdit.additionalEdit = insert.additionalEdit; return pasteEdit; @@ -84,7 +84,6 @@ class DropOrPasteEditProvider implements vscode.DocumentPasteEditProvider, vscod } const dropEdit = new vscode.DocumentDropEdit(insert.insertText); - dropEdit.id = this.id; dropEdit.yieldTo = [{ mimeType: MimeType.plain }]; dropEdit.additionalEdit = insert.additionalEdit; dropEdit.label = vscode.l10n.t('Insert Image as Attachment'); @@ -300,12 +299,14 @@ export function notebookImagePasteSetup(): vscode.Disposable { const provider = new DropOrPasteEditProvider(); return vscode.Disposable.from( vscode.languages.registerDocumentPasteEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, provider, { + id: provider.id, pasteMimeTypes: [ MimeType.png, MimeType.uriList, ], }), vscode.languages.registerDocumentDropEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, provider, { + id: provider.id, dropMimeTypes: [ ...Object.values(imageExtToMime), MimeType.uriList, diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts index 111753897e3..934162b4daf 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts @@ -9,11 +9,11 @@ import { createEditForMediaFiles, createEditAddingLinksForUriList, mediaMimes, g class PasteEditProvider implements vscode.DocumentPasteEditProvider { - private readonly _id = 'insertLink'; + public static readonly id = 'insertLink'; private readonly _yieldTo = [ { mimeType: 'text/plain' }, - { extensionId: 'vscode.ipynb', editId: 'insertAttachment' }, + { extensionId: 'vscode.ipynb', providerId: 'insertAttachment' }, ]; async provideDocumentPasteEdits( @@ -32,7 +32,7 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider { return createEdit; } - const uriEdit = new vscode.DocumentPasteEdit('', this._id, ''); + const uriEdit = new vscode.DocumentPasteEdit('', ''); const urlList = await dataTransfer.get('text/uri-list')?.asString(); if (!urlList) { return; @@ -65,7 +65,7 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider { return; } - const pasteEdit = new vscode.DocumentPasteEdit(edit.snippet, this._id, edit.label); + const pasteEdit = new vscode.DocumentPasteEdit(edit.snippet, edit.label); pasteEdit.additionalEdit = edit.additionalEdits; pasteEdit.yieldTo = this._yieldTo; return pasteEdit; @@ -74,6 +74,7 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider { export function registerPasteSupport(selector: vscode.DocumentSelector,) { return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteEditProvider(), { + id: PasteEditProvider.id, pasteMimeTypes: [ 'text/uri-list', ...mediaMimes, diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts index d6f0ae7ff36..4da71d20690 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts @@ -10,7 +10,7 @@ const textPlainMime = 'text/plain'; class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { - readonly id = 'insertMarkdownLink'; + public static readonly id = 'insertMarkdownLink'; async provideDocumentPasteEdits( document: vscode.TextDocument, @@ -34,7 +34,7 @@ class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { return; } - const edit = new vscode.DocumentPasteEdit('', this.id, pasteEdit.label); + const edit = new vscode.DocumentPasteEdit('', pasteEdit.label); edit.additionalEdit = pasteEdit.additionalEdits; edit.yieldTo = pasteEdit.markdownLink ? undefined : [{ mimeType: textPlainMime }]; return edit; @@ -43,6 +43,7 @@ class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { export function registerLinkPasteSupport(selector: vscode.DocumentSelector,) { return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteLinkEditProvider(), { + id: PasteLinkEditProvider.id, pasteMimeTypes: [textPlainMime] }); } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropIntoEditor.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropIntoEditor.ts index 9e202dde759..ad7f6d7cb0b 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropIntoEditor.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropIntoEditor.ts @@ -10,11 +10,11 @@ import { Schemes } from '../../util/schemes'; class MarkdownImageDropProvider implements vscode.DocumentDropEditProvider { - private readonly _id = 'insertLink'; + public static readonly id = 'insertLink'; private readonly _yieldTo = [ { mimeType: 'text/plain' }, - { extensionId: 'vscode.ipynb', editId: 'insertAttachment' }, + { extensionId: 'vscode.ipynb', providerId: 'insertAttachment' }, ]; async provideDocumentDropEdits(document: vscode.TextDocument, _position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { @@ -46,7 +46,6 @@ class MarkdownImageDropProvider implements vscode.DocumentDropEditProvider { } const edit = new vscode.DocumentDropEdit(snippet.snippet); - edit.id = this._id; edit.label = snippet.label; edit.yieldTo = this._yieldTo; return edit; @@ -68,7 +67,6 @@ class MarkdownImageDropProvider implements vscode.DocumentDropEditProvider { } const edit = new vscode.DocumentDropEdit(filesEdit.snippet); - edit.id = this._id; edit.label = filesEdit.label; edit.additionalEdit = filesEdit.additionalEdits; edit.yieldTo = this._yieldTo; @@ -78,6 +76,7 @@ class MarkdownImageDropProvider implements vscode.DocumentDropEditProvider { export function registerDropIntoEditorSupport(selector: vscode.DocumentSelector) { return vscode.languages.registerDocumentDropEditProvider(selector, new MarkdownImageDropProvider(), { + id: MarkdownImageDropProvider.id, dropMimeTypes: [ 'text/uri-list', ...mediaMimes, diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/documentPaste.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/documentPaste.test.ts index dd5b9eae41c..c2cdd073d74 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/documentPaste.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/documentPaste.test.ts @@ -37,7 +37,7 @@ suite.skip('vscode API - Copy Paste', function () { dataTransfer.set(textPlain, new vscode.DataTransferItem(reversed)); } } - }, { copyMimeTypes: [textPlain] })); + }, { id: 'test', copyMimeTypes: [textPlain] })); await vscode.commands.executeCommand('editor.action.clipboardCopyAction'); const newDocContent = getNextDocumentText(testDisposables, doc); @@ -62,7 +62,7 @@ suite.skip('vscode API - Copy Paste', function () { dataTransfer.set(textPlain, new vscode.DataTransferItem(reversed + '\n')); } } - }, { copyMimeTypes: [textPlain] })); + }, { id: 'test', copyMimeTypes: [textPlain] })); await vscode.commands.executeCommand('editor.action.clipboardCopyAction'); const newDocContent = getNextDocumentText(testDisposables, doc); @@ -88,7 +88,7 @@ suite.skip('vscode API - Copy Paste', function () { dataTransfer.set(textPlain, new vscode.DataTransferItem(`(${ranges.length})${selections.join(' ')}`)); } } - }, { copyMimeTypes: [textPlain] })); + }, { id: 'test', copyMimeTypes: [textPlain] })); await vscode.commands.executeCommand('editor.action.clipboardCopyAction'); editor.selections = [new vscode.Selection(0, 0, 0, 0)]; @@ -118,7 +118,7 @@ suite.skip('vscode API - Copy Paste', function () { dataTransfer.set(textPlain, new vscode.DataTransferItem('a')); providerAResolve(); } - }, { copyMimeTypes: [textPlain] })); + }, { id: 'test', copyMimeTypes: [textPlain] })); // Later registered providers will be called first testDisposables.push(vscode.languages.registerDocumentPasteEditProvider({ language: 'plaintext' }, new class implements vscode.DocumentPasteEditProvider { @@ -132,7 +132,7 @@ suite.skip('vscode API - Copy Paste', function () { dataTransfer.set(textPlain, new vscode.DataTransferItem('b')); } - }, { copyMimeTypes: [textPlain] })); + }, { id: 'test', copyMimeTypes: [textPlain] })); await vscode.commands.executeCommand('editor.action.clipboardCopyAction'); const newDocContent = getNextDocumentText(testDisposables, doc); @@ -159,7 +159,7 @@ suite.skip('vscode API - Copy Paste', function () { dataTransfer.set(textPlain, new vscode.DataTransferItem('xyz')); providerAResolve(); } - }, { copyMimeTypes: [textPlain] })); + }, { id: 'test', copyMimeTypes: [textPlain] })); testDisposables.push(vscode.languages.registerDocumentPasteEditProvider({ language: 'plaintext' }, new class implements vscode.DocumentPasteEditProvider { async prepareDocumentPaste(_document: vscode.TextDocument, _ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, _token: vscode.CancellationToken): Promise { @@ -172,7 +172,7 @@ suite.skip('vscode API - Copy Paste', function () { const str = await entry!.asString(); dataTransfer.set(textPlain, new vscode.DataTransferItem(reverseString(str))); } - }, { copyMimeTypes: [textPlain] })); + }, { id: 'test', copyMimeTypes: [textPlain] })); await vscode.commands.executeCommand('editor.action.clipboardCopyAction'); const newDocContent = getNextDocumentText(testDisposables, doc); @@ -192,13 +192,13 @@ suite.skip('vscode API - Copy Paste', function () { async prepareDocumentPaste(_document: vscode.TextDocument, _ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, _token: vscode.CancellationToken): Promise { dataTransfer.set(textPlain, new vscode.DataTransferItem('xyz')); } - }, { copyMimeTypes: [textPlain] })); + }, { id: 'test', copyMimeTypes: [textPlain] })); testDisposables.push(vscode.languages.registerDocumentPasteEditProvider({ language: 'plaintext' }, new class implements vscode.DocumentPasteEditProvider { async prepareDocumentPaste(_document: vscode.TextDocument, _ranges: readonly vscode.Range[], _dataTransfer: vscode.DataTransfer, _token: vscode.CancellationToken): Promise { throw new Error('Expected testing error from bad provider'); } - }, { copyMimeTypes: [textPlain] })); + }, { id: 'test', copyMimeTypes: [textPlain] })); await vscode.commands.executeCommand('editor.action.clipboardCopyAction'); const newDocContent = getNextDocumentText(testDisposables, doc); diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 4b36a70ef2f..409b993d05b 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -802,7 +802,6 @@ export interface CodeActionProvider { * @internal */ export interface DocumentPasteEdit { - readonly id: string; readonly label: string; readonly detail: string; readonly handledMimeType?: string; @@ -816,7 +815,7 @@ export interface DocumentPasteEdit { */ export interface DocumentPasteEditProvider { - readonly id?: string; + readonly id: string; readonly copyMimeTypes?: readonly string[]; readonly pasteMimeTypes?: readonly string[]; @@ -2012,13 +2011,12 @@ export enum ExternalUriOpenerPriority { /** * @internal */ -export type DropYieldTo = { readonly editId: string } | { readonly mimeType: string }; +export type DropYieldTo = { readonly providerId: string } | { readonly mimeType: string }; /** * @internal */ export interface DocumentOnDropEdit { - readonly id: string; readonly label: string; readonly handledMimeType?: string; readonly yieldTo?: readonly DropYieldTo[]; @@ -2030,6 +2028,7 @@ export interface DocumentOnDropEdit { * @internal */ export interface DocumentOnDropEditProvider { + readonly id?: string; readonly dropMimeTypes?: readonly string[]; provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): ProviderResult; diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts index 132a3777ab5..b0659a87385 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts @@ -284,12 +284,6 @@ export class CopyPasteController extends Disposable implements IEditorContributi return; } - // If the only edit returned is a text edit, use the default paste handler - if (providerEdits.length === 1 && providerEdits[0].id === 'text') { - await this.applyDefaultPasteHandler(dataTransfer, metadata, tokenSource.token); - return; - } - if (providerEdits.length) { const canShowWidget = editor.getOption(EditorOption.pasteAs).showPasteSelector === 'afterPaste'; return this._postPasteWidgetManager.applyEditAndShowIfNeeded(selections, { activeEditIndex: 0, allEdits: providerEdits }, canShowWidget, tokenSource.token); @@ -324,7 +318,11 @@ export class CopyPasteController extends Disposable implements IEditorContributi } // Filter out any providers the don't match the full data transfer we will send them. - const supportedProviders = allProviders.filter(provider => isSupportedPasteProvider(provider, dataTransfer)); + let supportedProviders = allProviders.filter(provider => isSupportedPasteProvider(provider, dataTransfer)); + if (preferredId) { + // We are looking for a specific edit + supportedProviders = supportedProviders.filter(edit => edit.id === preferredId); + } const providerEdits = await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, tokenSource.token); if (tokenSource.token.isCancellationRequested) { @@ -336,14 +334,13 @@ export class CopyPasteController extends Disposable implements IEditorContributi } let pickedEdit: DocumentPasteEdit | undefined; - if (typeof preferredId === 'string') { - // We are looking for a specific edit - pickedEdit = providerEdits.find(edit => edit.id === preferredId); + if (preferredId) { + pickedEdit = providerEdits.at(0); } else { const selected = await this._quickInputService.pick( providerEdits.map((edit): IQuickPickItem & { edit: DocumentPasteEdit } => ({ label: edit.label, - description: edit.id, + description: edit.providerId, detail: edit.detail, edit, })), { @@ -431,15 +428,18 @@ export class CopyPasteController extends Disposable implements IEditorContributi } } - private async getPasteEdits(providers: readonly DocumentPasteEditProvider[], dataTransfer: VSDataTransfer, model: ITextModel, selections: readonly Selection[], token: CancellationToken): Promise { + private async getPasteEdits(providers: readonly DocumentPasteEditProvider[], dataTransfer: VSDataTransfer, model: ITextModel, selections: readonly Selection[], token: CancellationToken): Promise> { const results = await raceCancellation( - Promise.all(providers.map(provider => { + Promise.all(providers.map(async provider => { try { - return provider.provideDocumentPasteEdits?.(model, selections, dataTransfer, token); + const edit = await provider.provideDocumentPasteEdits?.(model, selections, dataTransfer, token); + if (edit) { + return { ...edit, providerId: provider.id }; + } } catch (err) { console.error(err); - return undefined; } + return undefined; })), token); const edits = coalesce(results ?? []); diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts index 92c3b9742bc..69ff4dc3181 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts @@ -29,12 +29,12 @@ abstract class SimplePasteAndDropProvider implements DocumentOnDropEditProvider, async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { const edit = await this.getEdit(dataTransfer, token); - return edit ? { id: this.id, insertText: edit.insertText, label: edit.label, detail: edit.detail, handledMimeType: edit.handledMimeType, yieldTo: edit.yieldTo } : undefined; + return edit ? { insertText: edit.insertText, label: edit.label, detail: edit.detail, handledMimeType: edit.handledMimeType, yieldTo: edit.yieldTo } : undefined; } async provideDocumentOnDropEdits(_model: ITextModel, _position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { const edit = await this.getEdit(dataTransfer, token); - return edit ? { id: this.id, insertText: edit.insertText, label: edit.label, handledMimeType: edit.handledMimeType, yieldTo: edit.yieldTo } : undefined; + return edit ? { insertText: edit.insertText, label: edit.label, handledMimeType: edit.handledMimeType, yieldTo: edit.yieldTo } : undefined; } protected abstract getEdit(dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise; @@ -60,7 +60,6 @@ class DefaultTextProvider extends SimplePasteAndDropProvider { const insertText = await textEntry.asString(); return { - id: this.id, handledMimeType: Mimes.text, label: localize('text.label', "Insert Plain Text"), detail: builtInLabel, @@ -107,7 +106,6 @@ class PathProvider extends SimplePasteAndDropProvider { } return { - id: this.id, handledMimeType: Mimes.uriList, insertText, label, @@ -144,7 +142,6 @@ class RelativePathProvider extends SimplePasteAndDropProvider { } return { - id: this.id, handledMimeType: Mimes.uriList, insertText: relativeUris.join(' '), label: entries.length > 1 diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts index b4fc47ef451..ca3ed7dfabb 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts @@ -125,8 +125,16 @@ export class DropIntoEditorController extends Disposable implements IEditorContr } private async getDropEdits(providers: DocumentOnDropEditProvider[], model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, tokenSource: EditorStateCancellationTokenSource) { - const results = await raceCancellation(Promise.all(providers.map(provider => { - return provider.provideDocumentOnDropEdits(model, position, dataTransfer, tokenSource.token); + const results = await raceCancellation(Promise.all(providers.map(async provider => { + try { + const edit = await provider.provideDocumentOnDropEdits(model, position, dataTransfer, tokenSource.token); + if (edit) { + return { ...edit, providerId: provider.id }; + } + } catch (err) { + console.error(err); + } + return undefined; })), tokenSource.token); const edits = coalesce(results ?? []); sortEditsByYieldTo(edits); diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/edit.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/edit.ts index 312eb58a9f0..f117d88549a 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/edit.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/edit.ts @@ -29,12 +29,12 @@ export function createCombinedWorkspaceEdit(uri: URI, ranges: readonly Range[], } export function sortEditsByYieldTo(edits: T[]): void { function yieldsTo(yTo: DropYieldTo, other: T): boolean { - return ('editId' in yTo && yTo.editId === other.id) + return ('providerId' in yTo && yTo.providerId === other.providerId) || ('mimeType' in yTo && yTo.mimeType === other.handledMimeType); } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 548cc6b8517..84247a03e27 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -372,8 +372,8 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread private readonly _pasteEditProviders = new Map(); - $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], metadata: IPasteEditProviderMetadataDto): void { - const provider = new MainThreadPasteEditProvider(handle, this._proxy, metadata, this._uriIdentService); + $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], id: string, metadata: IPasteEditProviderMetadataDto): void { + const provider = new MainThreadPasteEditProvider(handle, this._proxy, id, metadata, this._uriIdentService); this._pasteEditProviders.set(handle, provider); this._registrations.set(handle, combinedDisposable( this._languageFeaturesService.documentPasteEditProvider.register(selector, provider), @@ -915,8 +915,8 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread private readonly _documentOnDropEditProviders = new Map(); - $registerDocumentOnDropEditProvider(handle: number, selector: IDocumentFilterDto[], metadata: IDocumentDropEditProviderMetadata): void { - const provider = new MainThreadDocumentOnDropEditProvider(handle, metadata, this._proxy, this._uriIdentService); + $registerDocumentOnDropEditProvider(handle: number, selector: IDocumentFilterDto[], id: string | undefined, metadata: IDocumentDropEditProviderMetadata): void { + const provider = new MainThreadDocumentOnDropEditProvider(handle, this._proxy, id, metadata, this._uriIdentService); this._documentOnDropEditProviders.set(handle, provider); this._registrations.set(handle, combinedDisposable( this._languageFeaturesService.documentOnDropEditProvider.register(selector, provider), @@ -937,6 +937,7 @@ class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider private readonly dataTransfers = new DataTransferFileCache(); + public readonly id: string; public readonly copyMimeTypes?: readonly string[]; public readonly pasteMimeTypes?: readonly string[]; @@ -944,11 +945,13 @@ class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider readonly provideDocumentPasteEdits?: languages.DocumentPasteEditProvider['provideDocumentPasteEdits']; constructor( - private readonly handle: number, + private readonly _handle: number, private readonly _proxy: ExtHostLanguageFeaturesShape, + id: string, metadata: IPasteEditProviderMetadataDto, @IUriIdentityService private readonly _uriIdentService: IUriIdentityService ) { + this.id = id; this.copyMimeTypes = metadata.copyMimeTypes; this.pasteMimeTypes = metadata.pasteMimeTypes; @@ -959,7 +962,7 @@ class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider return undefined; } - const newDataTransfer = await this._proxy.$prepareDocumentPaste(handle, model.uri, selections, dataTransferDto, token); + const newDataTransfer = await this._proxy.$prepareDocumentPaste(_handle, model.uri, selections, dataTransferDto, token); if (!newDataTransfer) { return undefined; } @@ -981,7 +984,7 @@ class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider return; } - const result = await this._proxy.$providePasteEdits(this.handle, request.id, model.uri, selections, dataTransferDto, token); + const result = await this._proxy.$providePasteEdits(this._handle, request.id, model.uri, selections, dataTransferDto, token); if (!result) { return; } @@ -1006,14 +1009,17 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentOnDropEd private readonly dataTransfers = new DataTransferFileCache(); + readonly id: string | undefined; readonly dropMimeTypes?: readonly string[]; constructor( - private readonly handle: number, - metadata: IDocumentDropEditProviderMetadata | undefined, + private readonly _handle: number, private readonly _proxy: ExtHostLanguageFeaturesShape, + id: string | undefined, + metadata: IDocumentDropEditProviderMetadata | undefined, @IUriIdentityService private readonly _uriIdentService: IUriIdentityService ) { + this.id = id; this.dropMimeTypes = metadata?.dropMimeTypes ?? ['*/*']; } @@ -1025,7 +1031,7 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentOnDropEd return; } - const edit = await this._proxy.$provideDocumentOnDropEdits(this.handle, request.id, model.uri, position, dataTransferDto, token); + const edit = await this._proxy.$provideDocumentOnDropEdits(this._handle, request.id, model.uri, position, dataTransferDto, token); if (!edit) { return; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index eb1958e9fba..7bbb622aad5 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -402,7 +402,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerLinkedEditingRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void; $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string, supportsResolve: boolean): void; - $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], metadata: IPasteEditProviderMetadataDto): void; + $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], id: string, metadata: IPasteEditProviderMetadataDto): void; $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; $registerRangeFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string, supportRanges: boolean): void; $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; @@ -423,7 +423,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerSelectionRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerTypeHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void; - $registerDocumentOnDropEditProvider(handle: number, selector: IDocumentFilterDto[], metadata?: IDocumentDropEditProviderMetadata): void; + $registerDocumentOnDropEditProvider(handle: number, selector: IDocumentFilterDto[], id: string | undefined, metadata?: IDocumentDropEditProviderMetadata): void; $resolvePasteFileData(handle: number, requestId: number, dataId: string): Promise; $resolveDocumentOnDropFileData(handle: number, requestId: number, dataId: string): Promise; $setLanguageConfiguration(handle: number, languageId: string, configuration: ILanguageConfigurationDto): void; @@ -1894,12 +1894,11 @@ export interface IPasteEditProviderMetadataDto { } export interface IPasteEditDto { - id: string; label: string; detail: string; - yieldTo?: readonly languages.DropYieldTo[]; insertText: string | { snippet: string }; additionalEdit?: IWorkspaceEditDto; + yieldTo?: readonly languages.DropYieldTo[]; } export interface IDocumentDropEditProviderMetadata { @@ -1907,11 +1906,10 @@ export interface IDocumentDropEditProviderMetadata { } export interface IDocumentOnDropEditDto { - id: string; label: string; - yieldTo?: readonly languages.DropYieldTo[]; insertText: string | { snippet: string }; additionalEdit?: IWorkspaceEditDto; + yieldTo?: readonly languages.DropYieldTo[]; } export interface ExtHostLanguageFeaturesShape { diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index c3c91bbff98..359877e5509 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -506,6 +506,10 @@ class CodeActionAdapter { class DocumentPasteEditProvider { + public static toInternalProviderId(extId: string, editId: string): string { + return extId + '.' + editId; + } + constructor( private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape, private readonly _documents: ExtHostDocuments, @@ -553,20 +557,15 @@ class DocumentPasteEditProvider { } return { - id: edit.id ? this.toInternalId(this._extension.identifier.value, edit.id) : this._extension.identifier.value, label: edit.label ?? localize('defaultPasteLabel', "Paste using '{0}' extension", this._extension.displayName || this._extension.name), detail: this._extension.displayName || this._extension.name, yieldTo: edit.yieldTo?.map(yTo => { - return 'mimeType' in yTo ? yTo : { editId: this.toInternalId(yTo.extensionId, yTo.editId) }; + return 'mimeType' in yTo ? yTo : { providerId: DocumentPasteEditProvider.toInternalProviderId(yTo.extensionId, yTo.providerId) }; }), insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value }, additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined, }; } - - private toInternalId(extId: string, editId: string): string { - return extId + '.' + editId; - } } class DocumentFormattingAdapter { @@ -1785,6 +1784,10 @@ class TypeHierarchyAdapter { class DocumentOnDropEditAdapter { + public static toInternalProviderId(extId: string, editId: string): string { + return extId + '.' + editId; + } + constructor( private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape, private readonly _documents: ExtHostDocuments, @@ -1805,19 +1808,14 @@ class DocumentOnDropEditAdapter { return undefined; } return { - id: edit.id ? this.toInternalId(this._extension.identifier.value, edit.id) : this._extension.identifier.value, label: edit.label ?? localize('defaultDropLabel', "Drop using '{0}' extension", this._extension.displayName || this._extension.name), yieldTo: edit.yieldTo?.map(yTo => { - return 'mimeType' in yTo ? yTo : { editId: this.toInternalId(yTo.extensionId, yTo.editId) }; + return 'mimeType' in yTo ? yTo : { providerId: DocumentOnDropEditAdapter.toInternalProviderId(yTo.extensionId, yTo.providerId) }; }), insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value }, additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined, }; } - - private toInternalId(extId: string, editId: string): string { - return extId + '.' + editId; - } } type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter @@ -2454,7 +2452,8 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF const handle = this._nextHandle(); this._adapter.set(handle, new AdapterData(new DocumentOnDropEditAdapter(this._proxy, this._documents, provider, handle, extension), extension)); - this._proxy.$registerDocumentOnDropEditProvider(handle, this._transformDocumentSelector(selector, extension), isProposedApiEnabled(extension, 'dropMetadata') ? metadata : undefined); + const id = isProposedApiEnabled(extension, 'dropMetadata') && metadata ? DocumentOnDropEditAdapter.toInternalProviderId(extension.identifier.value, metadata.id) : undefined; + this._proxy.$registerDocumentOnDropEditProvider(handle, this._transformDocumentSelector(selector, extension), id, isProposedApiEnabled(extension, 'dropMetadata') ? metadata : undefined); return this._createDisposable(handle); } @@ -2469,7 +2468,8 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF registerDocumentPasteEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider, metadata: vscode.DocumentPasteProviderMetadata): vscode.Disposable { const handle = this._nextHandle(); this._adapter.set(handle, new AdapterData(new DocumentPasteEditProvider(this._proxy, this._documents, provider, handle, extension), extension)); - this._proxy.$registerPasteEditProvider(handle, this._transformDocumentSelector(selector, extension), { + const internalId = DocumentPasteEditProvider.toInternalProviderId(extension.identifier.value, metadata.id); + this._proxy.$registerPasteEditProvider(handle, this._transformDocumentSelector(selector, extension), internalId, { supportsCopy: !!provider.prepareDocumentPaste, supportsPaste: !!provider.provideDocumentPasteEdits, copyMimeTypes: metadata.copyMimeTypes, diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 057aee98ad4..cf0a8633f80 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2755,16 +2755,12 @@ export class DocumentDropEdit { @es5ClassCompat export class DocumentPasteEdit { - id: string; label: string; - insertText: string | SnippetString; - additionalEdit?: WorkspaceEdit; - constructor(insertText: string | SnippetString, id: string, label: string) { - this.id = id; + constructor(insertText: string | SnippetString, label: string) { this.label = label; this.insertText = insertText; } diff --git a/src/vscode-dts/vscode.proposed.documentPaste.d.ts b/src/vscode-dts/vscode.proposed.documentPaste.d.ts index 261c730dc9b..4b5e49ab330 100644 --- a/src/vscode-dts/vscode.proposed.documentPaste.d.ts +++ b/src/vscode-dts/vscode.proposed.documentPaste.d.ts @@ -44,12 +44,6 @@ declare module 'vscode' { * An operation applied on paste */ class DocumentPasteEdit { - /** - * Identifies the type of edit. - * - * This id should be unique within the extension but does not need to be unique across extensions. - */ - id: string; /** * Human readable label that describes the edit. @@ -60,7 +54,7 @@ declare module 'vscode' { * Controls the ordering or multiple paste edits. If this provider yield to edits, it will be shown lower in the list. */ yieldTo?: ReadonlyArray< - | { readonly extensionId: string; readonly editId: string } + | { readonly extensionId: string; readonly providerId: string } | { readonly mimeType: string } >; @@ -79,10 +73,19 @@ declare module 'vscode' { * * TODO: Reverse args, but this will break existing consumers :( */ - constructor(insertText: string | SnippetString, id: string, label: string); + constructor(insertText: string | SnippetString, label: string); } interface DocumentPasteProviderMetadata { + /** + * Identifies the provider. + * + * This id is used when users configure the default provider for paste. + * + * This id should be unique within the extension but does not need to be unique across extensions. + */ + readonly id: string; + /** * Mime types that {@link DocumentPasteEditProvider.prepareDocumentPaste provideDocumentPasteEdits} may add on copy. */ diff --git a/src/vscode-dts/vscode.proposed.dropMetadata.d.ts b/src/vscode-dts/vscode.proposed.dropMetadata.d.ts index 3161226bb93..b78790d85a6 100644 --- a/src/vscode-dts/vscode.proposed.dropMetadata.d.ts +++ b/src/vscode-dts/vscode.proposed.dropMetadata.d.ts @@ -8,16 +8,6 @@ declare module 'vscode' { // https://github.com/microsoft/vscode/issues/179430 export interface DocumentDropEdit { - /** - * Identifies the type of edit. - * - * This id should be unique within the extension but does not need to be unique across extensions. - * - * TODO: Should this live on the provider instead? That way we could call just the provider we want (however it would - * prevent extending providers in the future to allow returning multiple edits) - */ - id?: string; - /** * Human readable label that describes the edit. */ @@ -35,14 +25,23 @@ declare module 'vscode' { */ yieldTo?: ReadonlyArray< // TODO: what about built-in providers? - | { readonly extensionId: string; readonly editId: string } + | { readonly extensionId: string; readonly providerId: string } | { readonly mimeType: string } >; } export interface DocumentDropEditProviderMetadata { /** - * List of data transfer types that the provider supports. + * Identifies the provider. + * + * This id is used when users configure the default provider for drop. + * + * This id should be unique within the extension but does not need to be unique across extensions. + */ + readonly id: string; + + /** + * List of {@link DataTransfer} mime types that the provider can handle. * * This can either be an exact mime type such as `image/png`, or a wildcard pattern such as `image/*`. * From 49ac847cc899736737fdb3a1f977754a5303da0f Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Tue, 8 Aug 2023 14:47:20 -0700 Subject: [PATCH 26/69] bugfix for actionWidget hovers (#189955) * bugfixes on actionWidget.css * added selector in ListWidget styling --- src/vs/base/browser/ui/list/listWidget.ts | 2 +- src/vs/platform/actionWidget/browser/actionWidget.css | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 36c5a74a948..c02a2a53afe 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -830,7 +830,7 @@ export class DefaultStyleController implements IStyleController { } if (styles.listFocusForeground) { - content.push(`.monaco-list${suffix}:focus .monaco-list-row.focused { color: ${styles.listFocusForeground}; }`); + content.push(`.monaco-list${suffix}:focus .monaco-list-row.focused:not(.monaco-list-row.action.option-disabled) { color: ${styles.listFocusForeground}; }`); } if (styles.listActiveSelectionBackground) { diff --git a/src/vs/platform/actionWidget/browser/actionWidget.css b/src/vs/platform/actionWidget/browser/actionWidget.css index f84f3ef757a..7308524f84e 100644 --- a/src/vs/platform/actionWidget/browser/actionWidget.css +++ b/src/vs/platform/actionWidget/browser/actionWidget.css @@ -92,12 +92,9 @@ align-items: center; } -.action-widget .monaco-list-row.action.option-disabled { - color: var(--vscode-disabledForeground); -} - +.action-widget .monaco-list-row.action.option-disabled, .action-widget .monaco-list-row.action.option-disabled .codicon { - opacity: 0.4; + color: var(--vscode-disabledForeground); } .action-widget .monaco-list-row.action:not(.option-disabled) .codicon { From ce652fbb3adcba13622e03910fe2ee6563377886 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 8 Aug 2023 15:16:03 -0700 Subject: [PATCH 27/69] Enable sash for webview find widget (#189977) Fixes #189648 --- .../workbench/contrib/webview/browser/webviewFindWidget.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts index 848fcd1fbe5..b98f6bcddce 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts @@ -33,7 +33,11 @@ export class WebviewFindWidget extends SimpleFindWidget { @IContextKeyService contextKeyService: IContextKeyService, @IKeybindingService keybindingService: IKeybindingService ) { - super({ showCommonFindToggles: false, checkImeCompletionState: _delegate.checkImeCompletionState }, contextViewService, contextKeyService, keybindingService); + super({ + showCommonFindToggles: false, + checkImeCompletionState: _delegate.checkImeCompletionState, + enableSash: true, + }, contextViewService, contextKeyService, keybindingService); this._findWidgetFocused = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED.bindTo(contextKeyService); this._register(_delegate.hasFindResult(hasResult => { From ce417fac2a657858003802519943fbd91c068da3 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 8 Aug 2023 15:31:32 -0700 Subject: [PATCH 28/69] `withUndefinedAsNull(x)` -> `x ?? null` (#189983) Replaces `withUndefinedAsNull` with the `??` operator --- src/vs/base/common/types.ts | 6 ------ .../dropOrPasteInto/browser/copyPasteController.ts | 3 +-- src/vs/platform/quickinput/browser/quickInput.ts | 4 ++-- src/vs/workbench/api/common/extHostWorkspace.ts | 11 +++++------ src/vs/workbench/browser/parts/editor/editorStatus.ts | 4 ++-- src/vs/workbench/browser/parts/editor/titleControl.ts | 6 +++--- src/vs/workbench/common/editor/textEditorModel.ts | 3 +-- .../contrib/comments/browser/commentsController.ts | 4 ++-- .../debug/browser/debugConfigurationManager.ts | 7 +++---- .../contrib/debug/browser/debugTaskRunner.ts | 3 +-- .../workbench/contrib/debug/browser/variablesView.ts | 3 +-- .../contrib/files/browser/views/openEditorsView.ts | 3 +-- .../workbench/contrib/markers/browser/markersModel.ts | 3 +-- .../workbench/contrib/markers/browser/markersView.ts | 3 +-- .../contrib/preferences/browser/settingsEditor2.ts | 8 ++++---- .../contrib/preferences/browser/settingsTreeModels.ts | 4 ++-- .../configuration/common/configurationEditing.ts | 4 ++-- 17 files changed, 32 insertions(+), 47 deletions(-) diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index c66ad018097..cd17ebe8775 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -197,12 +197,6 @@ export function withNullAsUndefined(x: T | null): T | undefined { return x === null ? undefined : x; } -/** - * Converts undefined to null, passes all other values through. - */ -export function withUndefinedAsNull(x: T | undefined): T | null { - return typeof x === 'undefined' ? null : x; -} type AddFirstParameterToFunction = T extends (...args: any[]) => TargetFunctionsReturnType ? // Function: add param to function diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts index b0659a87385..30e79607eb1 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts @@ -11,7 +11,6 @@ import { UriList, VSDataTransfer, createStringDataTransferItem, matchesMimeType import { Disposable } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import * as platform from 'vs/base/common/platform'; -import { withUndefinedAsNull } from 'vs/base/common/types'; import { generateUuid } from 'vs/base/common/uuid'; import { ClipboardEventUtils } from 'vs/editor/browser/controller/textAreaInput'; import { toExternalVSDataTransfer, toVSDataTransfer } from 'vs/editor/browser/dnd'; @@ -395,7 +394,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi return { defaultPastePayload: { mode: metadata.mode, - multicursorText: withUndefinedAsNull(metadata.multicursorText), + multicursorText: metadata.multicursorText ?? null, pasteOnNewLine: !!metadata.isFromEmptySelection, }, }; diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 7beb2f1e868..621bed2b3e2 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -27,7 +27,7 @@ import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { isIOS } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { ThemeIcon } from 'vs/base/common/themables'; -import { isString, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; +import { isString, withNullAsUndefined } from 'vs/base/common/types'; import 'vs/css!./media/quickInput'; import { localize } from 'vs/nls'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; @@ -1077,7 +1077,7 @@ class QuickPick extends QuickInput implements IQuickPi } } if (this.ui.list.ariaLabel !== ariaLabel) { - this.ui.list.ariaLabel = withUndefinedAsNull(ariaLabel); + this.ui.list.ariaLabel = ariaLabel ?? null; } this.ui.list.matchOnDescription = this.matchOnDescription; this.ui.list.matchOnDetail = this.matchOnDetail; diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index c4f627da3c9..cd93d1743be 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -13,7 +13,6 @@ import { Schemas } from 'vs/base/common/network'; import { Counter } from 'vs/base/common/numbers'; import { basename, basenameOrAuthority, dirname, ExtUri, relativePath } from 'vs/base/common/resources'; import { compare } from 'vs/base/common/strings'; -import { withUndefinedAsNull } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -462,10 +461,10 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac const { includePattern, folder } = parseSearchInclude(GlobPattern.from(include)); return this._proxy.$startFileSearch( - withUndefinedAsNull(includePattern), - withUndefinedAsNull(folder), - withUndefinedAsNull(excludePatternOrDisregardExcludes), - withUndefinedAsNull(maxResults), + includePattern ?? null, + folder ?? null, + excludePatternOrDisregardExcludes ?? null, + maxResults ?? null, token ) .then(data => Array.isArray(data) ? data.map(d => URI.revive(d)) : []); @@ -541,7 +540,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac try { const result = await this._proxy.$startTextSearch( query, - withUndefinedAsNull(folder), + folder ?? null, queryOptions, requestId, token); diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 4938c99f3b9..4b8a497d2b1 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { format, compare, splitLines } from 'vs/base/common/strings'; import { extname, basename, isEqual } from 'vs/base/common/resources'; -import { areFunctions, assertIsDefined, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; +import { areFunctions, assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; import { Language } from 'vs/base/common/platform'; @@ -1110,7 +1110,7 @@ export class ChangeLanguageAction extends Action2 { // User decided to configure settings for current language if (pick === configureLanguageSettings) { - preferencesService.openUserSettings({ jsonEditor: true, revealSetting: { key: `[${withUndefinedAsNull(currentLanguageId)}]`, edit: true } }); + preferencesService.openUserSettings({ jsonEditor: true, revealSetting: { key: `[${currentLanguageId ?? null}]`, edit: true } }); return; } diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index ab517fcd4be..0bfa78864d0 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -33,7 +33,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ResourceContextKey, ActiveEditorPinnedContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, ActiveEditorAvailableEditorIdsContext, applyAvailableEditorIds } from 'vs/workbench/common/contextkeys'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IFileService } from 'vs/platform/files/common/files'; -import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types'; +import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; import { isFirefox } from 'vs/base/browser/browser'; import { isCancellationError } from 'vs/base/common/errors'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; @@ -251,7 +251,7 @@ export abstract class TitleControl extends Themable { this.contextKeyService.bufferChangeEvents(() => { const activeEditor = this.group.activeEditor; - this.resourceContext.set(withUndefinedAsNull(EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }))); + this.resourceContext.set(EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY } ?? null)); this.editorPinnedContext.set(activeEditor ? this.group.isPinned(activeEditor) : false); this.editorIsFirstContext.set(activeEditor ? this.group.isFirst(activeEditor) : false); @@ -360,7 +360,7 @@ export abstract class TitleControl extends Themable { // Update contexts based on editor picked and remember previous to restore const currentResourceContext = this.resourceContext.get(); - this.resourceContext.set(withUndefinedAsNull(EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.PRIMARY }))); + this.resourceContext.set(EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.PRIMARY } ?? null)); const currentPinnedContext = !!this.editorPinnedContext.get(); this.editorPinnedContext.set(this.group.isPinned(editor)); const currentEditorIsFirstContext = !!this.editorIsFirstContext.get(); diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index 0a2ba0af243..44454299bad 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -12,7 +12,6 @@ import { ILanguageService, ILanguageSelection } from 'vs/editor/common/languages import { IModelService } from 'vs/editor/common/services/model'; import { MutableDisposable } from 'vs/base/common/lifecycle'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; -import { withUndefinedAsNull } from 'vs/base/common/types'; import { ILanguageDetectionService, LanguageDetectionLanguageEventSource } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService'; import { ThrottledDelayer } from 'vs/base/common/async'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -188,7 +187,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel // lookup language via resource path if the provided language is unspecific if (!preferredLanguage || preferredLanguage === PLAINTEXT_LANGUAGE_ID) { - return languageService.createByFilepathOrFirstLine(withUndefinedAsNull(resource), firstLineText); + return languageService.createByFilepathOrFirstLine(resource ?? null, firstLineText); } // otherwise take the preferred language for granted diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index b596a2c27de..97df0066099 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -37,7 +37,7 @@ import { Position } from 'vs/editor/common/core/position'; import { CommentThreadRangeDecorator } from 'vs/workbench/contrib/comments/browser/commentThreadRangeDecorator'; import { ICursorSelectionChangedEvent } from 'vs/editor/common/cursorEvents'; import { CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsView'; -import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; +import { withNullAsUndefined } from 'vs/base/common/types'; export const ID = 'editor.contrib.review'; @@ -405,7 +405,7 @@ export class CommentController implements IEditorContribution { } this._editorDisposables.push(this.editor.onMouseMove(e => this.onEditorMouseMove(e))); this._editorDisposables.push(this.editor.onDidChangeCursorPosition(e => this.onEditorChangeCursorPosition(e.position))); - this._editorDisposables.push(this.editor.onDidFocusEditorWidget(() => this.onEditorChangeCursorPosition(withUndefinedAsNull(this.editor?.getPosition())))); + this._editorDisposables.push(this.editor.onDidFocusEditorWidget(() => this.onEditorChangeCursorPosition(this.editor?.getPosition() ?? null))); this._editorDisposables.push(this.editor.onDidChangeCursorSelection(e => this.onEditorChangeCursorSelection(e))); this._editorDisposables.push(this.editor.onDidBlurEditorWidget(() => this.onEditorChangeCursorSelection())); } diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 1c90fa41c86..bd2a52708d1 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -12,7 +12,6 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { DisposableStore, IDisposable, dispose } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import * as resources from 'vs/base/common/resources'; -import { withUndefinedAsNull } from 'vs/base/common/types'; import { URI as uri } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -621,7 +620,7 @@ class Launch extends AbstractLaunch implements ILaunch { }, ACTIVE_GROUP); return ({ - editor: withUndefinedAsNull(editor), + editor: editor ?? null, created }); } @@ -681,7 +680,7 @@ class WorkspaceLaunch extends AbstractLaunch implements ILaunch { }, ACTIVE_GROUP); return ({ - editor: withUndefinedAsNull(editor), + editor: editor ?? null, created: false }); } @@ -721,7 +720,7 @@ class UserLaunch extends AbstractLaunch implements ILaunch { async openConfigFile({ preserveFocus, type, useInitialContent }: { preserveFocus: boolean; type?: string; useInitialContent?: boolean }): Promise<{ editor: IEditorPane | null; created: boolean }> { const editor = await this.preferencesService.openUserSettings({ jsonEditor: true, preserveFocus, revealSetting: { key: 'launch' } }); return ({ - editor: withUndefinedAsNull(editor), + editor: editor ?? null, created: false }); } diff --git a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts index e3eb8299230..5026c9d18af 100644 --- a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts +++ b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts @@ -12,7 +12,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { ITaskEvent, TaskEventKind, ITaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { withUndefinedAsNull } from 'vs/base/common/types'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; import { IViewsService } from 'vs/workbench/common/views'; @@ -235,7 +234,7 @@ export class DebugTaskRunner { return inactivePromise; } - return taskPromise.then(withUndefinedAsNull); + return taskPromise.then(x => x ?? null); }); return new Promise((c, e) => { diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 528e5b51fe8..b7d7e3da4ff 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -16,7 +16,6 @@ import { RunOnceScheduler, timeout } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { withUndefinedAsNull } from 'vs/base/common/types'; import { localize } from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -127,7 +126,7 @@ export class VariablesView extends ViewPane { } }); - this.tree.setInput(withUndefinedAsNull(this.debugService.getViewModel().focusedStackFrame)); + this.tree.setInput(this.debugService.getViewModel().focusedStackFrame ?? null); CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService); diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 7501450cf73..3304ca86197 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -37,7 +37,6 @@ import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewl import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; import { memoize } from 'vs/base/common/decorators'; import { ElementsDragAndDropData, NativeDragAndDropData } from 'vs/base/browser/ui/list/listView'; -import { withUndefinedAsNull } from 'vs/base/common/types'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; @@ -259,7 +258,7 @@ export class OpenEditorsView extends ViewPane { const resource = element.getResource(); this.dirtyEditorFocusedContext.set(element.editor.isDirty() && !element.editor.isSaving()); this.readonlyEditorFocusedContext.set(!!element.editor.isReadonly()); - this.resourceContext.set(withUndefinedAsNull(resource)); + this.resourceContext.set(resource ?? null); } else if (!!element) { this.groupFocusedContext.set(true); } diff --git a/src/vs/workbench/contrib/markers/browser/markersModel.ts b/src/vs/workbench/contrib/markers/browser/markersModel.ts index f47c6b81f3a..ad0e29114f2 100644 --- a/src/vs/workbench/contrib/markers/browser/markersModel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersModel.ts @@ -11,7 +11,6 @@ import { isNonEmptyArray, flatten } from 'vs/base/common/arrays'; import { ResourceMap } from 'vs/base/common/map'; import { Emitter, Event } from 'vs/base/common/event'; import { Hasher } from 'vs/base/common/hash'; -import { withUndefinedAsNull } from 'vs/base/common/types'; import { splitLines } from 'vs/base/common/strings'; import { IMatch } from 'vs/base/common/filters'; import { unsupportedSchemas } from 'vs/platform/markers/common/markerService'; @@ -183,7 +182,7 @@ export class MarkersModel { } getResourceMarkers(resource: URI): ResourceMarkers | null { - return withUndefinedAsNull(this.resourcesByUri.get(extUri.getComparisonKey(resource, true))); + return this.resourcesByUri.get(extUri.getComparisonKey(resource, true)) ?? null; } setResourceMarkers(resourcesMarkers: [URI, IMarker[]][]): void { diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index fd436d62639..1e7bf9e7073 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -36,7 +36,6 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { withUndefinedAsNull } from 'vs/base/common/types'; import { MementoObject, Memento } from 'vs/workbench/common/memento'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -652,7 +651,7 @@ export class MarkersView extends FilterViewPane implements IMarkersView { private setCurrentActiveEditor(): void { const activeEditor = this.editorService.activeEditor; - this.currentActiveResource = activeEditor ? withUndefinedAsNull(EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY })) : null; + this.currentActiveResource = activeEditor ? EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }) ?? null : null; } private onSelected(): void { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 80924f64cef..315476bf3be 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -19,7 +19,7 @@ import { Iterable } from 'vs/base/common/iterator'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; -import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; +import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/settingsEditor2'; import { localize } from 'vs/nls'; @@ -395,7 +395,7 @@ export class SettingsEditor2 extends EditorPane { this.editorMemento.clearEditorState(this.input, this.group); } - return withUndefinedAsNull(cachedState); + return cachedState ?? null; } override getViewState(): object | undefined { @@ -830,7 +830,7 @@ export class SettingsEditor2 extends EditorPane { })); this._register(this.tocTree.onDidChangeFocus(e => { - const element: SettingsTreeGroupElement | null = withUndefinedAsNull(e.elements?.[0]); + const element: SettingsTreeGroupElement | null = e.elements?.[0] ?? null; if (this.tocFocusedElement === element) { return; } @@ -937,7 +937,7 @@ export class SettingsEditor2 extends EditorPane { if (classList && classList.contains('monaco-list') && classList.contains('settings-editor-tree')) { this._currentFocusContext = SettingsFocusContext.SettingTree; this.settingRowFocused.set(true); - this.treeFocusedElement ??= withUndefinedAsNull(this.settingsTree.firstVisibleElement); + this.treeFocusedElement ??= this.settingsTree.firstVisibleElement ?? null; if (this.treeFocusedElement) { this.treeFocusedElement.tabbable = true; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index e55eba8fa77..95142f4dbef 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -5,7 +5,7 @@ import * as arrays from 'vs/base/common/arrays'; import { escapeRegExpCharacters, isFalsyOrWhitespace } from 'vs/base/common/strings'; -import { withUndefinedAsNull, isUndefinedOrNull } from 'vs/base/common/types'; +import { isUndefinedOrNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget, IConfigurationValue } from 'vs/platform/configuration/common/configuration'; import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; @@ -530,7 +530,7 @@ export class SettingsTreeModel { } getElementsByName(name: string): SettingsTreeSettingElement[] | null { - return withUndefinedAsNull(this._treeElementsBySettingName.get(name)); + return this._treeElementsBySettingName.get(name) ?? null; } updateElementsByName(name: string): void { diff --git a/src/vs/workbench/services/configuration/common/configurationEditing.ts b/src/vs/workbench/services/configuration/common/configurationEditing.ts index adbf60a3ef6..2d42e3db59c 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditing.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditing.ts @@ -20,7 +20,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenSettingsOptions, IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { withUndefinedAsNull, withNullAsUndefined } from 'vs/base/common/types'; +import { withNullAsUndefined } from 'vs/base/common/types'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { ITextModel } from 'vs/editor/common/model'; import { IReference } from 'vs/base/common/lifecycle'; @@ -622,7 +622,7 @@ export class ConfigurationEditing { if (target === EditableConfigurationTarget.WORKSPACE) { if (workbenchState === WorkbenchState.WORKSPACE) { - return withUndefinedAsNull(workspace.configuration); + return workspace.configuration ?? null; } if (workbenchState === WorkbenchState.FOLDER) { return workspace.folders[0].toResource(relativePath); From caebd5c3a762de3741d801d980a816052adc0c82 Mon Sep 17 00:00:00 2001 From: David Dossett Date: Tue, 8 Aug 2023 16:19:23 -0700 Subject: [PATCH 29/69] Tune secondary button colors (#189989) --- extensions/theme-defaults/themes/dark_modern.json | 4 ++-- extensions/theme-defaults/themes/light_modern.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/theme-defaults/themes/dark_modern.json b/extensions/theme-defaults/themes/dark_modern.json index 6fe962b9611..b98d513c5c0 100644 --- a/extensions/theme-defaults/themes/dark_modern.json +++ b/extensions/theme-defaults/themes/dark_modern.json @@ -16,9 +16,9 @@ "button.border": "#FFFFFF12", "button.foreground": "#FFFFFF", "button.hoverBackground": "#026EC1", - "button.secondaryBackground": "#1F1F1F", + "button.secondaryBackground": "#313131", "button.secondaryForeground": "#CCCCCC", - "button.secondaryHoverBackground": "#2B2B2B", + "button.secondaryHoverBackground": "#3C3C3C", "chat.slashCommandBackground": "#3E4C56", "chat.slashCommandForeground": "#7FC3FF", "checkbox.background": "#313131", diff --git a/extensions/theme-defaults/themes/light_modern.json b/extensions/theme-defaults/themes/light_modern.json index f97435899a0..d5bf68ba831 100644 --- a/extensions/theme-defaults/themes/light_modern.json +++ b/extensions/theme-defaults/themes/light_modern.json @@ -18,7 +18,7 @@ "button.hoverBackground": "#0258A8", "button.secondaryBackground": "#E5E5E5", "button.secondaryForeground": "#3B3B3B", - "button.secondaryHoverBackground": "#CBCBCB", + "button.secondaryHoverBackground": "#CCCCCC", "chat.slashCommandBackground": "#D2ECFF", "chat.slashCommandForeground": "#306CA2", "checkbox.background": "#F8F8F8", From 388d3683ea12e82668e6bb7d851c17bebdfd9486 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 8 Aug 2023 16:27:21 -0700 Subject: [PATCH 30/69] Small cleanups in markdown drop/paste (#189985) - Observer cancellation more places - Create constants for mime types - Making code more consistent to show duplication --- .../src/extension.shared.ts | 6 +-- ...pIntoEditor.ts => dropResourceProvider.ts} | 38 +++++++++---------- ...{copyPaste.ts => pasteResourceProvider.ts} | 35 ++++++++++------- ...{copyPasteLinks.ts => pasteUrlProvider.ts} | 18 ++++----- .../src/languageFeatures/copyFiles/shared.ts | 18 ++------- .../src/util/mimes.ts | 22 +++++++++++ 6 files changed, 77 insertions(+), 60 deletions(-) rename extensions/markdown-language-features/src/languageFeatures/copyFiles/{dropIntoEditor.ts => dropResourceProvider.ts} (75%) rename extensions/markdown-language-features/src/languageFeatures/copyFiles/{copyPaste.ts => pasteResourceProvider.ts} (69%) rename extensions/markdown-language-features/src/languageFeatures/copyFiles/{copyPasteLinks.ts => pasteUrlProvider.ts} (84%) create mode 100644 extensions/markdown-language-features/src/util/mimes.ts diff --git a/extensions/markdown-language-features/src/extension.shared.ts b/extensions/markdown-language-features/src/extension.shared.ts index 79242573136..0d6c160bffe 100644 --- a/extensions/markdown-language-features/src/extension.shared.ts +++ b/extensions/markdown-language-features/src/extension.shared.ts @@ -7,10 +7,10 @@ import * as vscode from 'vscode'; import { MdLanguageClient } from './client/client'; import { CommandManager } from './commandManager'; import { registerMarkdownCommands } from './commands/index'; -import { registerPasteSupport } from './languageFeatures/copyFiles/copyPaste'; -import { registerLinkPasteSupport } from './languageFeatures/copyFiles/copyPasteLinks'; +import { registerPasteSupport } from './languageFeatures/copyFiles/pasteResourceProvider'; +import { registerLinkPasteSupport } from './languageFeatures/copyFiles/pasteUrlProvider'; import { registerDiagnosticSupport } from './languageFeatures/diagnostics'; -import { registerDropIntoEditorSupport } from './languageFeatures/copyFiles/dropIntoEditor'; +import { registerDropIntoEditorSupport } from './languageFeatures/copyFiles/dropResourceProvider'; import { registerFindFileReferenceSupport } from './languageFeatures/fileReferences'; import { registerUpdateLinksOnRename } from './languageFeatures/linkUpdater'; import { ILogger } from './logging'; diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropIntoEditor.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropResourceProvider.ts similarity index 75% rename from extensions/markdown-language-features/src/languageFeatures/copyFiles/dropIntoEditor.ts rename to extensions/markdown-language-features/src/languageFeatures/copyFiles/dropResourceProvider.ts index ad7f6d7cb0b..1c5a19461cf 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropIntoEditor.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropResourceProvider.ts @@ -4,14 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { createEditForMediaFiles as createEditForMediaFiles, mediaMimes, tryGetUriListSnippet } from './shared'; +import { Mime, mediaMimes } from '../../util/mimes'; import { Schemes } from '../../util/schemes'; +import { createEditForMediaFiles, tryGetUriListSnippet } from './shared'; - -class MarkdownImageDropProvider implements vscode.DocumentDropEditProvider { +class ResourceDropProvider implements vscode.DocumentDropEditProvider { public static readonly id = 'insertLink'; + public static readonly dropMimeTypes = [ + Mime.textUriList, + ...mediaMimes, + ]; + private readonly _yieldTo = [ { mimeType: 'text/plain' }, { extensionId: 'vscode.ipynb', providerId: 'insertAttachment' }, @@ -36,10 +41,11 @@ class MarkdownImageDropProvider implements vscode.DocumentDropEditProvider { } private async _getUriListEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { - const urlList = await dataTransfer.get('text/uri-list')?.asString(); - if (!urlList) { + const urlList = await dataTransfer.get(Mime.textUriList)?.asString(); + if (!urlList || token.isCancellationRequested) { return undefined; } + const snippet = await tryGetUriListSnippet(document, urlList, token); if (!snippet) { return undefined; @@ -61,25 +67,19 @@ class MarkdownImageDropProvider implements vscode.DocumentDropEditProvider { return; } - const filesEdit = await createEditForMediaFiles(document, dataTransfer, token); - if (!filesEdit) { + const edit = await createEditForMediaFiles(document, dataTransfer, token); + if (!edit) { return; } - const edit = new vscode.DocumentDropEdit(filesEdit.snippet); - edit.label = filesEdit.label; - edit.additionalEdit = filesEdit.additionalEdits; - edit.yieldTo = this._yieldTo; - return edit; + const dropEdit = new vscode.DocumentDropEdit(edit.snippet); + dropEdit.label = edit.label; + dropEdit.additionalEdit = edit.additionalEdits; + dropEdit.yieldTo = this._yieldTo; + return dropEdit; } } export function registerDropIntoEditorSupport(selector: vscode.DocumentSelector) { - return vscode.languages.registerDocumentDropEditProvider(selector, new MarkdownImageDropProvider(), { - id: MarkdownImageDropProvider.id, - dropMimeTypes: [ - 'text/uri-list', - ...mediaMimes, - ] - }); + return vscode.languages.registerDocumentDropEditProvider(selector, new ResourceDropProvider(), ResourceDropProvider); } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteResourceProvider.ts similarity index 69% rename from extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts rename to extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteResourceProvider.ts index 934162b4daf..cd051210d2d 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteResourceProvider.ts @@ -4,13 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { Mime, mediaMimes } from '../../util/mimes'; import { Schemes } from '../../util/schemes'; -import { createEditForMediaFiles, createEditAddingLinksForUriList, mediaMimes, getPasteUrlAsFormattedLinkSetting, PasteUrlAsFormattedLink } from './shared'; +import { PasteUrlAsFormattedLink, createEditAddingLinksForUriList, createEditForMediaFiles, getPasteUrlAsFormattedLinkSetting } from './shared'; -class PasteEditProvider implements vscode.DocumentPasteEditProvider { +class PasteResourceEditProvider implements vscode.DocumentPasteEditProvider { public static readonly id = 'insertLink'; + public static readonly pasteMimeTypes = [ + Mime.textUriList, + ...mediaMimes, + ]; + private readonly _yieldTo = [ { mimeType: 'text/plain' }, { extensionId: 'vscode.ipynb', providerId: 'insertAttachment' }, @@ -32,19 +38,26 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider { return createEdit; } - const uriEdit = new vscode.DocumentPasteEdit('', ''); - const urlList = await dataTransfer.get('text/uri-list')?.asString(); - if (!urlList) { + if (token.isCancellationRequested) { + return; + } + + return this._getUriListEdit(document, ranges, dataTransfer, token); + } + + private async _getUriListEdit(document: vscode.TextDocument, ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { + const uriList = await dataTransfer.get(Mime.textUriList)?.asString(); + if (!uriList || token.isCancellationRequested) { return; } const pasteUrlSetting = getPasteUrlAsFormattedLinkSetting(document); - const pasteEdit = await createEditAddingLinksForUriList(document, ranges, urlList, false, pasteUrlSetting === PasteUrlAsFormattedLink.Smart, token); + const pasteEdit = await createEditAddingLinksForUriList(document, ranges, uriList, false, pasteUrlSetting === PasteUrlAsFormattedLink.Smart, token); if (!pasteEdit) { return; } - uriEdit.label = pasteEdit.label; + const uriEdit = new vscode.DocumentPasteEdit('', pasteEdit.label); uriEdit.additionalEdit = pasteEdit.additionalEdits; uriEdit.yieldTo = this._yieldTo; return uriEdit; @@ -73,11 +86,5 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider { } export function registerPasteSupport(selector: vscode.DocumentSelector,) { - return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteEditProvider(), { - id: PasteEditProvider.id, - pasteMimeTypes: [ - 'text/uri-list', - ...mediaMimes, - ] - }); + return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteResourceEditProvider(), PasteResourceEditProvider); } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts similarity index 84% rename from extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts rename to extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index 4da71d20690..b68b9cdaa2b 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -4,14 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { Mime } from '../../util/mimes'; import { createEditAddingLinksForUriList, getPasteUrlAsFormattedLinkSetting, PasteUrlAsFormattedLink, validateLink } from './shared'; -const textPlainMime = 'text/plain'; - -class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { +class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { public static readonly id = 'insertMarkdownLink'; + public static readonly pasteMimeTypes = [ + Mime.textPlain, + ]; + async provideDocumentPasteEdits( document: vscode.TextDocument, ranges: readonly vscode.Range[], @@ -23,7 +26,7 @@ class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { return; } - const item = dataTransfer.get(textPlainMime); + const item = dataTransfer.get(Mime.textPlain); const urlList = await item?.asString(); if (token.isCancellationRequested || !urlList || !validateLink(urlList).isValid) { return; @@ -36,14 +39,11 @@ class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { const edit = new vscode.DocumentPasteEdit('', pasteEdit.label); edit.additionalEdit = pasteEdit.additionalEdits; - edit.yieldTo = pasteEdit.markdownLink ? undefined : [{ mimeType: textPlainMime }]; + edit.yieldTo = pasteEdit.markdownLink ? undefined : [{ mimeType: Mime.textPlain }]; return edit; } } export function registerLinkPasteSupport(selector: vscode.DocumentSelector,) { - return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteLinkEditProvider(), { - id: PasteLinkEditProvider.id, - pasteMimeTypes: [textPlainMime] - }); + return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteUrlEditProvider(), PasteUrlEditProvider); } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts index 0608698f5ca..ef4ec6b34be 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts @@ -6,10 +6,11 @@ import * as path from 'path'; import * as vscode from 'vscode'; import * as URI from 'vscode-uri'; -import { Schemes } from '../../util/schemes'; -import { NewFilePathGenerator } from './copyFiles'; import { coalesce } from '../../util/arrays'; import { getDocumentDir } from '../../util/document'; +import { mediaMimes } from '../../util/mimes'; +import { Schemes } from '../../util/schemes'; +import { NewFilePathGenerator } from './copyFiles'; enum MediaKind { Image, @@ -49,19 +50,6 @@ export const mediaFileExtensions = new Map([ ['wav', MediaKind.Audio], ]); -export const mediaMimes = new Set([ - 'image/bmp', - 'image/gif', - 'image/jpeg', - 'image/png', - 'image/webp', - 'video/mp4', - 'video/ogg', - 'audio/mpeg', - 'audio/aac', - 'audio/x-wav', -]); - const smartPasteRegexes = [ { regex: /(\[[^\[\]]*](?:\([^\(\)]*\)|\[[^\[\]]*]))/g }, // In a Markdown link { regex: /^```[\s\S]*?```$/gm }, // In a backtick fenced code block diff --git a/extensions/markdown-language-features/src/util/mimes.ts b/extensions/markdown-language-features/src/util/mimes.ts new file mode 100644 index 00000000000..8028294b3f4 --- /dev/null +++ b/extensions/markdown-language-features/src/util/mimes.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. + *--------------------------------------------------------------------------------------------*/ + +export const Mime = { + textUriList: 'text/uri-list', + textPlain: 'text/plain', +} as const; + +export const mediaMimes = new Set([ + 'image/bmp', + 'image/gif', + 'image/jpeg', + 'image/png', + 'image/webp', + 'video/mp4', + 'video/ogg', + 'audio/mpeg', + 'audio/aac', + 'audio/x-wav', +]); From 6e251255fb31fcb32a2ada2ceb247c35c189205d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 8 Aug 2023 16:45:52 -0700 Subject: [PATCH 31/69] `withNullAsUndefined(x)` -> `x ?? undefined` (#189987) Follow up on #189983 Replaces `withNullAsUndefined` with uses of `??` --- src/vs/base/browser/dom.ts | 3 +-- src/vs/base/browser/ui/menu/menubar.ts | 3 +-- src/vs/base/browser/ui/toolbar/toolbar.ts | 3 +-- src/vs/base/common/types.ts | 8 ------- .../contextmenu/electron-main/contextmenu.ts | 3 +-- src/vs/code/electron-main/auth.ts | 3 +-- src/vs/editor/browser/editorExtensions.ts | 6 ++--- .../editor/browser/widget/codeEditorWidget.ts | 3 +-- .../codeAction/browser/lightBulbWidget.ts | 5 ++-- .../browser/link/goToDefinitionAtPosition.ts | 3 +-- .../browser/editorNavigationQuickAccess.ts | 5 ++-- .../contrib/snippet/browser/snippetSession.ts | 3 +-- .../standaloneCommandsQuickAccess.ts | 3 +-- .../standaloneGotoLineQuickAccess.ts | 3 +-- .../standaloneGotoSymbolQuickAccess.ts | 3 +-- .../electron-main/dialogMainService.ts | 3 +-- src/vs/platform/dnd/browser/dnd.ts | 3 +-- .../issue/electron-main/issueMainService.ts | 3 +-- .../quickinput/browser/commandsQuickAccess.ts | 5 ++-- .../platform/quickinput/browser/quickInput.ts | 4 ++-- .../quickinput/browser/quickInputList.ts | 11 ++++----- .../electron-main/windowsMainService.ts | 4 ++-- .../workspacesManagementMainService.ts | 3 +-- src/vs/server/node/remoteTerminalChannel.ts | 3 +-- .../workbench/api/browser/mainThreadEditor.ts | 3 +-- .../workbench/api/browser/mainThreadTask.ts | 2 +- .../api/browser/mainThreadTerminalService.ts | 3 +-- .../api/browser/mainThreadWorkspace.ts | 5 ++-- src/vs/workbench/api/common/extHostChat.ts | 3 +-- .../workbench/api/common/extHostComments.ts | 3 +-- .../api/common/extHostDebugService.ts | 5 ++-- .../api/common/extHostTerminalService.ts | 23 +++++++++---------- .../api/common/extHostTypeConverters.ts | 8 +++---- .../browser/extHostLanguageFeatures.test.ts | 3 +-- .../workbench/browser/parts/compositePart.ts | 4 ++-- .../browser/parts/editor/breadcrumbsModel.ts | 3 +-- .../browser/parts/editor/editorAutoSave.ts | 3 +-- .../browser/parts/editor/editorCommands.ts | 8 +++---- .../browser/parts/editor/editorGroupView.ts | 5 ++-- .../browser/parts/editor/editorStatus.ts | 14 +++++------ .../parts/editor/noTabsTitleControl.ts | 4 ++-- .../browser/parts/editor/textCodeEditor.ts | 4 ++-- .../browser/parts/editor/textDiffEditor.ts | 4 ++-- .../parts/editor/textResourceEditor.ts | 4 ++-- .../browser/parts/editor/titleControl.ts | 4 ++-- .../browser/parts/titlebar/windowTitle.ts | 3 +-- .../common/editor/diffEditorInput.ts | 3 +-- .../browser/actions/chatAccessibilityHelp.ts | 3 +-- .../contrib/chat/browser/chatListRenderer.ts | 3 +-- .../contrib/chat/browser/chatWidget.ts | 3 +-- .../contrib/chat/common/chatServiceImpl.ts | 11 ++++----- .../browser/commentThreadZoneWidget.ts | 3 +-- .../comments/browser/commentsController.ts | 3 +-- .../browser/breakpointEditorContribution.ts | 3 +-- .../contrib/debug/browser/debugQuickAccess.ts | 3 +-- .../notebookExecutionStateServiceImpl.ts | 3 +-- .../preferences/browser/keybindingWidgets.ts | 3 +-- .../preferences/browser/settingsEditor2.ts | 3 +-- .../quickaccess/browser/viewQuickAccess.ts | 3 +-- .../contrib/scm/common/quickDiffService.ts | 3 +-- .../search/browser/anythingQuickAccess.ts | 3 +-- .../search/browser/searchActionsFind.ts | 3 +-- .../contrib/search/browser/searchView.ts | 3 +-- .../search/browser/symbolsQuickAccess.ts | 3 +-- .../searchEditor/browser/searchEditor.ts | 4 ++-- .../browser/searchEditorActions.ts | 3 +-- .../terminal/browser/baseTerminalBackend.ts | 3 +-- .../terminal/browser/terminalActions.ts | 4 ++-- .../terminal/browser/terminalInstance.ts | 5 ++-- .../browser/terminalProfileResolverService.ts | 3 +-- .../contrib/terminal/browser/terminalView.ts | 3 +-- .../common/remote/remoteTerminalChannel.ts | 3 +-- .../terminal/common/terminalEnvironment.ts | 6 ++--- .../electron-sandbox/localTerminalBackend.ts | 3 +-- .../browser/terminalAccessibleBuffer.ts | 5 ++-- .../webviewView/browser/webviewViewPane.ts | 3 +-- .../parts/titlebar/menubarControl.ts | 5 ++-- .../common/configurationEditing.ts | 9 ++++---- .../services/editor/browser/editorService.ts | 6 ++--- .../electron-sandbox/extensionHostProfiler.ts | 3 +-- .../localProcessExtensionHost.ts | 5 ++-- .../common/filesConfigurationService.ts | 3 +-- .../preferences/common/preferencesModels.ts | 3 +-- .../textfile/browser/textFileService.ts | 5 ++-- .../textfile/common/textFileEditorModel.ts | 4 ++-- .../common/untitledTextEditorModel.ts | 4 ++-- 86 files changed, 147 insertions(+), 221 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 249a479504b..a72e8e0ea85 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -15,7 +15,6 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { FileAccess, RemoteAuthorities, Schemas } from 'vs/base/common/network'; import * as platform from 'vs/base/common/platform'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; export function clearNode(node: HTMLElement): void { @@ -1284,7 +1283,7 @@ export function triggerUpload(): Promise { // Resolve once the input event has fired once event.Event.once(event.Event.fromDOMEventEmitter(input, 'input'))(() => { - resolve(withNullAsUndefined(input.files)); + resolve(input.files ?? undefined); }); input.click(); diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index fe7586f0d56..061003dbc9a 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -20,7 +20,6 @@ import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; -import { withNullAsUndefined } from 'vs/base/common/types'; import 'vs/css!./menubar'; import * as nls from 'vs/nls'; @@ -1020,7 +1019,7 @@ export class MenuBar extends Disposable { getKeyBinding: this.options.getKeybinding, actionRunner: this.actionRunner, enableMnemonics: this.options.alwaysOnMnemonics || (this.mnemonicsInUse && this.options.enableMnemonics), - ariaLabel: withNullAsUndefined(customMenu.buttonElement.getAttribute('aria-label')), + ariaLabel: customMenu.buttonElement.getAttribute('aria-label') ?? undefined, expandDirection: this.isCompact ? this.options.compactMode : Direction.Right, useEventAsContext: true }; diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 85936d72ce6..c91d4ee1b07 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -13,7 +13,6 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { EventMultiplexer } from 'vs/base/common/event'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { withNullAsUndefined } from 'vs/base/common/types'; import 'vs/css!./toolbar'; import * as nls from 'vs/nls'; @@ -196,7 +195,7 @@ export class ToolBar extends Disposable { private getKeybindingLabel(action: IAction): string | undefined { const key = this.lookupKeybindings ? this.options.getKeyBinding?.(action) : undefined; - return withNullAsUndefined(key?.getLabel()); + return key?.getLabel() ?? undefined; } private clear(): void { diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index cd17ebe8775..d35246667bc 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -190,14 +190,6 @@ export function validateConstraint(arg: unknown, constraint: TypeConstraint | un } } -/** - * Converts null to undefined, passes all other values through. - */ -export function withNullAsUndefined(x: T | null): T | undefined { - return x === null ? undefined : x; -} - - type AddFirstParameterToFunction = T extends (...args: any[]) => TargetFunctionsReturnType ? // Function: add param to function (firstArg: FirstParameter, ...args: Parameters) => ReturnType : diff --git a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts index 6b2eb1cb820..40b3d5c46d3 100644 --- a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts +++ b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts @@ -5,7 +5,6 @@ import { BrowserWindow, IpcMainEvent, Menu, MenuItem } from 'electron'; import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { CONTEXT_MENU_CHANNEL, CONTEXT_MENU_CLOSE_CHANNEL, IPopupOptions, ISerializableContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu'; export function registerContextMenuListener(): void { @@ -13,7 +12,7 @@ export function registerContextMenuListener(): void { const menu = createMenu(event, onClickChannel, items); menu.popup({ - window: withNullAsUndefined(BrowserWindow.fromWebContents(event.sender)), + window: BrowserWindow.fromWebContents(event.sender) ?? undefined, x: options ? options.x : undefined, y: options ? options.y : undefined, positioningItem: options ? options.positioningItem : undefined, diff --git a/src/vs/code/electron-main/auth.ts b/src/vs/code/electron-main/auth.ts index 6a18eba4eb5..9e6793fd489 100644 --- a/src/vs/code/electron-main/auth.ts +++ b/src/vs/code/electron-main/auth.ts @@ -8,7 +8,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { hash } from 'vs/base/common/hash'; import { Disposable } from 'vs/base/common/lifecycle'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { generateUuid } from 'vs/base/common/uuid'; import { ICredentialsMainService } from 'vs/platform/credentials/common/credentials'; import { IEncryptionMainService } from 'vs/platform/encryption/common/encryptionService'; @@ -153,7 +152,7 @@ export class ProxyAuthHandler extends Disposable { let encryptedSerializedProxyCredentials = this.applicationStorageMainService.get(this.PROXY_CREDENTIALS_SERVICE_KEY + authInfoHash, StorageScope.APPLICATION); let decryptedSerializedProxyCredentials: string | undefined; if (!encryptedSerializedProxyCredentials) { - encryptedSerializedProxyCredentials = withNullAsUndefined(await this.credentialsService.getPassword(this.OLD_PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash)); + encryptedSerializedProxyCredentials = await this.credentialsService.getPassword(this.OLD_PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash) ?? undefined; if (encryptedSerializedProxyCredentials) { // re-encrypt to force new encryption algorithm to apply decryptedSerializedProxyCredentials = await this.encryptionMainService.decrypt(encryptedSerializedProxyCredentials); diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 779468acc0a..75562c53821 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -19,7 +19,7 @@ import { ServicesAccessor as InstantiationServicesAccessor, BrandedService, IIns import { IKeybindings, KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { withNullAsUndefined, assertType } from 'vs/base/common/types'; +import { assertType } from 'vs/base/common/types'; import { ThemeIcon } from 'vs/base/common/themables'; import { IDisposable } from 'vs/base/common/lifecycle'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; @@ -307,7 +307,7 @@ export abstract class EditorCommand extends Command { return editor.invokeWithinContext((editorAccessor) => { const kbService = editorAccessor.get(IContextKeyService); - if (!kbService.contextMatchesRules(withNullAsUndefined(precondition))) { + if (!kbService.contextMatchesRules(precondition ?? undefined)) { // precondition does not hold return; } @@ -460,7 +460,7 @@ export abstract class EditorAction2 extends Action2 { return editor.invokeWithinContext((editorAccessor) => { const kbService = editorAccessor.get(IContextKeyService); const logService = editorAccessor.get(ILogService); - const enabled = kbService.contextMatchesRules(withNullAsUndefined(this.desc.precondition)); + const enabled = kbService.contextMatchesRules(this.desc.precondition ?? undefined); if (!enabled) { logService.debug(`[EditorAction2] NOT running command because its precondition is FALSE`, this.desc.id, this.desc.precondition?.serialize()); return; diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index f6389bf5410..a6f59231e39 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -48,7 +48,6 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; import { DOMLineBreaksComputerFactory } from 'vs/editor/browser/view/domLineBreaksComputer'; import { WordOperations } from 'vs/editor/common/cursor/cursorWordOperations'; @@ -345,7 +344,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE action.id, action.label, action.alias, - withNullAsUndefined(action.precondition), + action.precondition ?? undefined, (): Promise => { return this._instantiationService.invokeFunction((accessor) => { return Promise.resolve(action.runEditorCommand(accessor, this, null)); diff --git a/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts index 948e4d7d6bb..035c456ac5a 100644 --- a/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts @@ -9,7 +9,6 @@ import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; -import { withNullAsUndefined } from 'vs/base/common/types'; import 'vs/css!./lightBulbWidget'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -122,8 +121,8 @@ export class LightBulbWidget extends Disposable implements IContentWidget { })); this._register(Event.runAndSubscribe(keybindingService.onDidUpdateKeybindings, () => { - this._preferredKbLabel = withNullAsUndefined(keybindingService.lookupKeybinding(autoFixCommandId)?.getLabel()); - this._quickFixKbLabel = withNullAsUndefined(keybindingService.lookupKeybinding(quickFixCommandId)?.getLabel()); + this._preferredKbLabel = keybindingService.lookupKeybinding(autoFixCommandId)?.getLabel() ?? undefined; + this._quickFixKbLabel = keybindingService.lookupKeybinding(quickFixCommandId)?.getLabel() ?? undefined; this._updateLightBulbTitleAndIcon(); })); diff --git a/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts b/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts index a71163b6deb..f5a08f94c1e 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts @@ -9,7 +9,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { withNullAsUndefined } from 'vs/base/common/types'; import 'vs/css!./goToDefinitionAtPosition'; import { CodeEditorStateFlag, EditorState } from 'vs/editor/contrib/editorState/browser/editorState'; import { ICodeEditor, MouseTargetType } from 'vs/editor/browser/editorBrowser'; @@ -58,7 +57,7 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri this.toUnhook.add(linkGesture); this.toUnhook.add(linkGesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, keyboardEvent]) => { - this.startFindDefinitionFromMouse(mouseEvent, withNullAsUndefined(keyboardEvent)); + this.startFindDefinitionFromMouse(mouseEvent, keyboardEvent ?? undefined); })); this.toUnhook.add(linkGesture.onExecute((mouseEvent: ClickLinkMouseEvent) => { diff --git a/src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts index 4336914a3ce..d4082fc5a27 100644 --- a/src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts @@ -7,7 +7,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { getCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IRange } from 'vs/editor/common/core/range'; import { IDiffEditor, IEditor, ScrollType } from 'vs/editor/common/editorCommon'; @@ -95,9 +94,9 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu // changes even later because it could be that the user has // configured quick access to remain open when focus is lost and // we always want to restore the current location. - let lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState()); + let lastKnownEditorViewState = editor.saveViewState() ?? undefined; disposables.add(codeEditor.onDidChangeCursorPosition(() => { - lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState()); + lastKnownEditorViewState = editor.saveViewState() ?? undefined; })); context.restoreViewState = () => { diff --git a/src/vs/editor/contrib/snippet/browser/snippetSession.ts b/src/vs/editor/contrib/snippet/browser/snippetSession.ts index 78b6a87e8b2..80a9959f475 100644 --- a/src/vs/editor/contrib/snippet/browser/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/browser/snippetSession.ts @@ -7,7 +7,6 @@ import { groupBy } from 'vs/base/common/arrays'; import { CharCode } from 'vs/base/common/charCode'; import { dispose } from 'vs/base/common/lifecycle'; import { getLeadingWhitespace } from 'vs/base/common/strings'; -import { withNullAsUndefined } from 'vs/base/common/types'; import 'vs/css!./snippetSession'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -346,7 +345,7 @@ export class OneSnippet { let result: Range | undefined; const model = this._editor.getModel(); for (const decorationId of this._placeholderDecorations!.values()) { - const placeholderRange = withNullAsUndefined(model.getDecorationRange(decorationId)); + const placeholderRange = model.getDecorationRange(decorationId) ?? undefined; if (!result) { result = placeholderRange; } else { diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts index 5e87aa8f710..171a395e6f3 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts @@ -10,7 +10,6 @@ import { ICommandQuickPick } from 'vs/platform/quickinput/browser/commandsQuickA import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { AbstractEditorCommandsQuickAccessProvider } from 'vs/editor/contrib/quickAccess/browser/commandsQuickAccess'; import { IEditor } from 'vs/editor/common/editorCommon'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -24,7 +23,7 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export class StandaloneCommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { - protected get activeTextEditorControl(): IEditor | undefined { return withNullAsUndefined(this.codeEditorService.getFocusedCodeEditor()); } + protected get activeTextEditorControl(): IEditor | undefined { return this.codeEditorService.getFocusedCodeEditor() ?? undefined; } constructor( @IInstantiationService instantiationService: IInstantiationService, diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts index 71cfc2ec4ba..c6ccecf960c 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts @@ -7,7 +7,6 @@ import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAcce import { Registry } from 'vs/platform/registry/common/platform'; import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { GoToLineNLS } from 'vs/editor/common/standaloneStrings'; import { Event } from 'vs/base/common/event'; import { EditorAction, registerEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; @@ -25,7 +24,7 @@ export class StandaloneGotoLineQuickAccessProvider extends AbstractGotoLineQuick } protected get activeTextEditorControl() { - return withNullAsUndefined(this.editorService.getFocusedCodeEditor()); + return this.editorService.getFocusedCodeEditor() ?? undefined; } } diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts index d7bf0d94002..0c2cfb8617b 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts @@ -9,7 +9,6 @@ import { AbstractGotoSymbolQuickAccessProvider } from 'vs/editor/contrib/quickAc import { Registry } from 'vs/platform/registry/common/platform'; import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { QuickOutlineNLS } from 'vs/editor/common/standaloneStrings'; import { Event } from 'vs/base/common/event'; import { EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions'; @@ -34,7 +33,7 @@ export class StandaloneGotoSymbolQuickAccessProvider extends AbstractGotoSymbolQ } protected get activeTextEditorControl() { - return withNullAsUndefined(this.editorService.getFocusedCodeEditor()); + return this.editorService.getFocusedCodeEditor() ?? undefined; } } diff --git a/src/vs/platform/dialogs/electron-main/dialogMainService.ts b/src/vs/platform/dialogs/electron-main/dialogMainService.ts index d2b346a5e38..666829d56be 100644 --- a/src/vs/platform/dialogs/electron-main/dialogMainService.ts +++ b/src/vs/platform/dialogs/electron-main/dialogMainService.ts @@ -10,7 +10,6 @@ import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { normalizeNFC } from 'vs/base/common/normalization'; import { isMacintosh } from 'vs/base/common/platform'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { Promises } from 'vs/base/node/pfs'; import { localize } from 'vs/nls'; import { INativeOpenDialogOptions, massageMessageBoxOptions } from 'vs/platform/dialogs/common/dialogs'; @@ -106,7 +105,7 @@ export class DialogMainService implements IDialogMainService { } // Show Dialog - const result = await this.showOpenDialog(dialogOptions, withNullAsUndefined(window || BrowserWindow.getFocusedWindow())); + const result = await this.showOpenDialog(dialogOptions, (window || BrowserWindow.getFocusedWindow()) ?? undefined); if (result && result.filePaths && result.filePaths.length > 0) { return result.filePaths; } diff --git a/src/vs/platform/dnd/browser/dnd.ts b/src/vs/platform/dnd/browser/dnd.ts index 04d024b9675..2046ddb1143 100644 --- a/src/vs/platform/dnd/browser/dnd.ts +++ b/src/vs/platform/dnd/browser/dnd.ts @@ -12,7 +12,6 @@ import { ResourceMap } from 'vs/base/common/map'; import { parse } from 'vs/base/common/marshalling'; import { Schemas } from 'vs/base/common/network'; import { isWeb } from 'vs/base/common/platform'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -269,7 +268,7 @@ export async function extractFileListData(accessor: ServicesAccessor, files: Fil reader.onload = async event => { const name = file.name; - const loadResult = withNullAsUndefined(event.target?.result); + const loadResult = event.target?.result ?? undefined; if (typeof name !== 'string' || typeof loadResult === 'undefined') { result.complete(undefined); return; diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index dec29f8884c..14372990060 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -24,7 +24,6 @@ import { IIPCObjectUrl, IProtocolMainService } from 'vs/platform/protocol/electr import { zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; import { IWindowState } from 'vs/platform/window/electron-main/window'; import { randomPath } from 'vs/base/common/extpath'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { IStateService } from 'vs/platform/state/node/state'; import { UtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; @@ -286,7 +285,7 @@ export class IssueMainService implements IIssueMainService { message: localize('trace.message', "Successfully created the trace file"), detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), buttons: [localize({ key: 'trace.ok', comment: ['&& denotes a mnemonic'] }, "&&OK")], - }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); + }, BrowserWindow.getFocusedWindow() ?? undefined); // Show item in explorer this.nativeHostMainService.showItemInFolder(undefined, path); diff --git a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts index 97a5cb2ce5b..49e70c41c70 100644 --- a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -10,7 +10,6 @@ import { isCancellationError } from 'vs/base/common/errors'; import { matchesContiguousSubString, matchesPrefix, matchesWords, or } from 'vs/base/common/filters'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { LRUCache } from 'vs/base/common/map'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { localize } from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -69,8 +68,8 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc // Filter const filteredCommandPicks: ICommandQuickPick[] = []; for (const commandPick of allCommandPicks) { - const labelHighlights = withNullAsUndefined(AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.label)); - const aliasHighlights = commandPick.commandAlias ? withNullAsUndefined(AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.commandAlias)) : undefined; + const labelHighlights = AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.label) ?? undefined; + const aliasHighlights = commandPick.commandAlias ? AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.commandAlias) ?? undefined : undefined; // Add if matching in label or alias if (labelHighlights || aliasHighlights) { diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 621bed2b3e2..bede230413b 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -27,7 +27,7 @@ import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { isIOS } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { ThemeIcon } from 'vs/base/common/themables'; -import { isString, withNullAsUndefined } from 'vs/base/common/types'; +import { isString } from 'vs/base/common/types'; import 'vs/css!./media/quickInput'; import { localize } from 'vs/nls'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; @@ -1807,7 +1807,7 @@ export class QuickInputController extends Disposable { if (!focusChanged) { let currentElement = this.previousFocusElement; while (currentElement && !currentElement.offsetParent) { - currentElement = withNullAsUndefined(currentElement.parentElement); + currentElement = currentElement.parentElement ?? undefined; } if (currentElement?.offsetParent) { currentElement.focus(); diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index 643864da673..650219bb460 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -27,7 +27,6 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { ltrim } from 'vs/base/common/strings'; -import { withNullAsUndefined } from 'vs/base/common/types'; import 'vs/css!./media/quickInput'; import { localize } from 'vs/nls'; import { IQuickInputOptions } from 'vs/platform/quickinput/browser/quickInput'; @@ -291,7 +290,7 @@ class ListElementRenderer implements IListRenderer { let labelHighlights: IMatch[] | undefined; if (this.matchOnLabelMode === 'fuzzy') { - labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneLabel))) : undefined; + labelHighlights = this.matchOnLabel ? matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneLabel)) ?? undefined : undefined; } else { - labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesContiguousIconAware(queryWithWhitespace, parseLabelWithIcons(element.saneLabel))) : undefined; + labelHighlights = this.matchOnLabel ? matchesContiguousIconAware(queryWithWhitespace, parseLabelWithIcons(element.saneLabel)) ?? undefined : undefined; } - const descriptionHighlights = this.matchOnDescription ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDescription || ''))) : undefined; - const detailHighlights = this.matchOnDetail ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDetail || ''))) : undefined; + const descriptionHighlights = this.matchOnDescription ? matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDescription || '')) ?? undefined : undefined; + const detailHighlights = this.matchOnDetail ? matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDetail || '')) ?? undefined : undefined; if (labelHighlights || descriptionHighlights || detailHighlights) { element.labelHighlights = labelHighlights; diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 57d429c0f47..022a20e4ee0 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -21,7 +21,7 @@ import { getMarks, mark } from 'vs/base/common/performance'; import { IProcessEnvironment, isMacintosh, isWindows, OS } from 'vs/base/common/platform'; import { cwd } from 'vs/base/common/process'; import { extUriBiasedIgnorePathCase, isEqualAuthority, normalizePath, originalFSPath, removeTrailingPathSeparator } from 'vs/base/common/resources'; -import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; +import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; @@ -799,7 +799,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic detail: uri.scheme === Schemas.file ? localize('pathNotExistDetail', "The path '{0}' does not exist on this computer.", getPathLabel(uri, { os: OS, tildify: this.environmentMainService })) : localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", uri.toString(true)) - }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); + }, BrowserWindow.getFocusedWindow() ?? undefined); return undefined; })); diff --git a/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts index 39579b873fa..34913c5ffc5 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts @@ -10,7 +10,6 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { dirname, join } from 'vs/base/common/path'; import { basename, extUriBiasedIgnorePathCase, joinPath, originalFSPath } from 'vs/base/common/resources'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { Promises } from 'vs/base/node/pfs'; import { localize } from 'vs/nls'; @@ -281,7 +280,7 @@ export class WorkspacesManagementMainService extends Disposable implements IWork buttons: [localize({ key: 'ok', comment: ['&& denotes a mnemonic'] }, "&&OK")], message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(workspacePath)), detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again.") - }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); + }, BrowserWindow.getFocusedWindow() ?? undefined); return false; } diff --git a/src/vs/server/node/remoteTerminalChannel.ts b/src/vs/server/node/remoteTerminalChannel.ts index 0918a4855a2..3b08dc6bbd9 100644 --- a/src/vs/server/node/remoteTerminalChannel.ts +++ b/src/vs/server/node/remoteTerminalChannel.ts @@ -30,7 +30,6 @@ import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentServi import { IProductService } from 'vs/platform/product/common/productService'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { ILogService } from 'vs/platform/log/common/log'; class CustomVariableResolver extends AbstractVariableResolverService { @@ -242,7 +241,7 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel< } const envVariableCollections = new Map(entries); const mergedCollection = new MergedEnvironmentVariableCollection(envVariableCollections); - const workspaceFolder = activeWorkspaceFolder ? withNullAsUndefined(activeWorkspaceFolder) : undefined; + const workspaceFolder = activeWorkspaceFolder ? activeWorkspaceFolder ?? undefined : undefined; await mergedCollection.applyToProcessEnvironment(env, { workspaceFolder }, variableResolver); } diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index 05b1a4921c9..449791c204b 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -16,7 +16,6 @@ import { IModelService } from 'vs/editor/common/services/model'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, IUndoStopOptions, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol'; import { IEditorPane } from 'vs/workbench/common/editor'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { equals } from 'vs/base/common/arrays'; import { CodeEditorStateFlag, EditorState } from 'vs/editor/contrib/editorState/browser/editorState'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -110,7 +109,7 @@ export class MainThreadTextEditorProperties { if (!oldProps || !MainThreadTextEditorProperties._selectionsEqual(oldProps.selections, this.selections)) { delta.selections = { selections: this.selections, - source: withNullAsUndefined(selectionChangeSource) + source: selectionChangeSource ?? undefined, }; } diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 18163ed9261..8db946cea29 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -284,7 +284,7 @@ namespace TaskSourceDTO { scope = value.scope; } else { scope = TaskScope.Folder; - workspaceFolder = Types.withNullAsUndefined(workspace.getWorkspaceFolder(URI.revive(value.scope))); + workspaceFolder = workspace.getWorkspaceFolder(URI.revive(value.scope)) ?? undefined; } const result: IExtensionTaskSource = { kind: TaskSourceKind.Extension, diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index da519c2c911..daab5df909d 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -17,7 +17,6 @@ import { IEnvironmentVariableService } from 'vs/workbench/contrib/terminal/commo import { deserializeEnvironmentDescriptionMap, deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariableShared'; import { IStartExtensionTerminalRequest, ITerminalProcessExtHostProxy, ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { OperatingSystem, OS } from 'vs/base/common/platform'; import { TerminalEditorLocationOptions } from 'vscode'; import { Promises } from 'vs/base/common/async'; @@ -121,7 +120,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } private async _updateDefaultProfile() { - const remoteAuthority = withNullAsUndefined(this._extHostContext.remoteAuthority); + const remoteAuthority = this._extHostContext.remoteAuthority ?? undefined; const defaultProfile = this._terminalProfileResolverService.getDefaultProfile({ remoteAuthority, os: this._os }); const defaultAutomationProfile = this._terminalProfileResolverService.getDefaultProfile({ remoteAuthority, os: this._os, allowAutomationShell: true }); this._proxy.$acceptDefaultProfile(...await Promise.all([defaultProfile, defaultAutomationProfile])); diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index 0c2b6f99b6a..8e97e6a954c 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -7,7 +7,6 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { isCancellationError } from 'vs/base/common/errors'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { isNative } from 'vs/base/common/platform'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -148,11 +147,11 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { const query = this._queryBuilder.file( includeFolder ? [includeFolder] : workspace.folders, { - maxResults: withNullAsUndefined(maxResults), + maxResults: maxResults ?? undefined, disregardExcludeSettings: (excludePatternOrDisregardExcludes === false) || undefined, disregardSearchExcludeSettings: true, disregardIgnoreFiles: true, - includePattern: withNullAsUndefined(includePattern), + includePattern: includePattern ?? undefined, excludePattern: typeof excludePatternOrDisregardExcludes === 'string' ? excludePatternOrDisregardExcludes : undefined, _reason: 'startFileSearch' }); diff --git a/src/vs/workbench/api/common/extHostChat.ts b/src/vs/workbench/api/common/extHostChat.ts index 6a4dd119dc9..df995d8fc23 100644 --- a/src/vs/workbench/api/common/extHostChat.ts +++ b/src/vs/workbench/api/common/extHostChat.ts @@ -9,7 +9,6 @@ import { Emitter } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { toDisposable } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { localize } from 'vs/nls'; import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; @@ -318,6 +317,6 @@ export class ExtHostChat implements ExtHostChatShape { } const resolved = await entry.provider.resolveSlashCommand(command, token); - return withNullAsUndefined(resolved); + return resolved ?? undefined; } } diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index c44e10e1acc..c1208be0537 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -20,7 +20,6 @@ import type * as vscode from 'vscode'; import { ExtHostCommentsShape, IMainContext, MainContext, CommentThreadChanges, CommentChanges } from './extHost.protocol'; import { ExtHostCommands } from './extHostCommands'; import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; -import { withNullAsUndefined } from 'vs/base/common/types'; type ProviderHandle = number; @@ -207,7 +206,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo fileComments: rangesResult.fileComments || false }; } else { - ranges = withNullAsUndefined(rangesResult); + ranges = rangesResult ?? undefined; } return ranges; }).then(ranges => { diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index a9d439ecc47..8eacd6f83b2 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -6,7 +6,6 @@ import { asPromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -475,9 +474,9 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E }); debugAdapter.onExit((code: number | null) => { if (tracker && tracker.onExit) { - tracker.onExit(withNullAsUndefined(code), undefined); + tracker.onExit(code ?? undefined, undefined); } - this._debugServiceProxy.$acceptDAExit(debugAdapterHandle, withNullAsUndefined(code), undefined); + this._debugServiceProxy.$acceptDAExit(debugAdapterHandle, code ?? undefined, undefined); }); if (tracker && tracker.onWillStartSession) { diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 1f95913ee42..40fdcbb2c87 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -21,7 +21,6 @@ import { IEnvironmentVariableCollectionDescription, IEnvironmentVariableMutator, import { ICreateContributedTerminalProfileOptions, IProcessReadyEvent, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, TerminalIcon, TerminalLocation, IProcessProperty, ProcessPropertyType, IProcessPropertyMap } from 'vs/platform/terminal/common/terminal'; import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering'; import { ThemeColor } from 'vs/base/common/themables'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { Promises } from 'vs/base/common/async'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { ViewColumn } from 'vs/workbench/api/common/extHostTypeConverters'; @@ -148,20 +147,20 @@ export class ExtHostTerminal { } await this._proxy.$createTerminal(this._id, { name: options.name, - shellPath: withNullAsUndefined(options.shellPath), - shellArgs: withNullAsUndefined(options.shellArgs), - cwd: withNullAsUndefined(options.cwd), - env: withNullAsUndefined(options.env), - icon: withNullAsUndefined(asTerminalIcon(options.iconPath)), + shellPath: options.shellPath ?? undefined, + shellArgs: options.shellArgs ?? undefined, + cwd: options.cwd ?? undefined, + env: options.env ?? undefined, + icon: asTerminalIcon(options.iconPath) ?? undefined, color: ThemeColor.isThemeColor(options.color) ? options.color.id : undefined, - initialText: withNullAsUndefined(options.message), - strictEnv: withNullAsUndefined(options.strictEnv), - hideFromUser: withNullAsUndefined(options.hideFromUser), - isFeatureTerminal: withNullAsUndefined(internalOptions?.isFeatureTerminal), + initialText: options.message ?? undefined, + strictEnv: options.strictEnv ?? undefined, + hideFromUser: options.hideFromUser ?? undefined, + isFeatureTerminal: internalOptions?.isFeatureTerminal ?? undefined, isExtensionOwnedTerminal: true, - useShellEnvironment: withNullAsUndefined(internalOptions?.useShellEnvironment), + useShellEnvironment: internalOptions?.useShellEnvironment ?? undefined, location: internalOptions?.location || this._serializeParentTerminal(options.location, internalOptions?.resolvedExtHostIdentifier), - isTransient: withNullAsUndefined(options.isTransient) + isTransient: options.isTransient ?? undefined, }); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index a4fa2e9340b..64b6623e79e 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -14,7 +14,7 @@ import { marked } from 'vs/base/common/marked/marked'; import { parse } from 'vs/base/common/marshalling'; import { Mimes } from 'vs/base/common/mime'; import { cloneAndChange } from 'vs/base/common/objects'; -import { isEmptyObject, isNumber, isString, isUndefinedOrNull, withNullAsUndefined } from 'vs/base/common/types'; +import { isEmptyObject, isNumber, isString, isUndefinedOrNull } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IURITransformer } from 'vs/base/common/uriIpc'; import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; @@ -1717,12 +1717,12 @@ export namespace NotebookExclusiveDocumentPattern { export function from(pattern: { include: vscode.GlobPattern | undefined | null; exclude: vscode.GlobPattern | undefined } | vscode.GlobPattern | undefined): string | extHostProtocol.IRelativePatternDto | { include: string | extHostProtocol.IRelativePatternDto | undefined; exclude: string | extHostProtocol.IRelativePatternDto | undefined } | undefined { if (isExclusivePattern(pattern)) { return { - include: withNullAsUndefined(GlobPattern.from(pattern.include)), - exclude: withNullAsUndefined(GlobPattern.from(pattern.exclude)) + include: GlobPattern.from(pattern.include) ?? undefined, + exclude: GlobPattern.from(pattern.exclude) ?? undefined, }; } - return withNullAsUndefined(GlobPattern.from(pattern)); + return GlobPattern.from(pattern) ?? undefined; } export function to(pattern: string | extHostProtocol.IRelativePatternDto | { include: string | extHostProtocol.IRelativePatternDto; exclude: string | extHostProtocol.IRelativePatternDto }): { include: vscode.GlobPattern; exclude: vscode.GlobPattern } | vscode.GlobPattern { diff --git a/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts b/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts index c0f47b0bc85..dc5f1061385 100644 --- a/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts @@ -45,7 +45,6 @@ import { provideSelectionRanges } from 'vs/editor/contrib/smartSelect/browser/sm import { mock } from 'vs/base/test/common/mock'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { dispose } from 'vs/base/common/lifecycle'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { NullApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { Progress } from 'vs/platform/progress/common/progress'; import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; @@ -1051,7 +1050,7 @@ suite('ExtHostLanguageFeatures', function () { const NullWorkerService = new class extends mock() { override computeMoreMinimalEdits(resource: URI, edits: languages.TextEdit[] | null | undefined): Promise { - return Promise.resolve(withNullAsUndefined(edits)); + return Promise.resolve(edits ?? undefined); } }; diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 742dcd68c79..de055f7b032 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -26,7 +26,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Dimension, append, $, hide, show } from 'vs/base/browser/dom'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; -import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; +import { assertIsDefined } from 'vs/base/common/types'; import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { AbstractProgressScope, ScopedProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; @@ -322,7 +322,7 @@ export abstract class CompositePart extends Part { const keybinding = this.keybindingService.lookupKeybinding(compositeId); - this.titleLabel.updateTitle(compositeId, compositeTitle, withNullAsUndefined(keybinding?.getLabel())); + this.titleLabel.updateTitle(compositeId, compositeTitle, keybinding?.getLabel() ?? undefined); const toolBar = assertIsDefined(this.toolBar); toolBar.setAriaLabel(localize('ariaCompositeToolbarLabel', "{0} actions", compositeTitle)); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index 67668bd3093..c056eb29fb3 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -14,7 +14,6 @@ import { Schemas } from 'vs/base/common/network'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { FileKind } from 'vs/platform/files/common/files'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { IOutline, IOutlineService, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; import { IEditorPane } from 'vs/workbench/common/editor'; import { matchesSomeScheme } from 'vs/platform/opener/common/opener'; @@ -125,7 +124,7 @@ export class BreadcrumbsModel { } const info: FileInfo = { - folder: withNullAsUndefined(this._workspaceService.getWorkspaceFolder(uri)), + folder: this._workspaceService.getWorkspaceFolder(uri) ?? undefined, path: [] }; diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts index c16023cc81a..1de67c853f8 100644 --- a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts +++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -11,7 +11,6 @@ import { SaveReason, IEditorIdentifier, GroupIdentifier, ISaveOptions, EditorInp import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { ILogService } from 'vs/platform/log/common/log'; @@ -75,7 +74,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution // Remember as last active const activeGroup = this.editorGroupService.activeGroup; - const activeEditor = this.lastActiveEditor = withNullAsUndefined(activeGroup.activeEditor); + const activeEditor = this.lastActiveEditor = activeGroup.activeEditor ?? undefined; this.lastActiveGroupId = activeGroup.id; // Dispose previous active control listeners diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 1153276c44f..044bd29da9e 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { isObject, isString, isUndefined, isNumber, withNullAsUndefined } from 'vs/base/common/types'; +import { isObject, isString, isUndefined, isNumber } from 'vs/base/common/types'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IEditorIdentifier, IEditorCommandsContext, CloseDirection, IVisibleEditorPane, EditorsOrder, EditorInputCapabilities, isEditorIdentifier, isEditorInputWithOptionsAndGroup, IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; @@ -749,7 +749,7 @@ export function splitEditor(editorGroupService: IEditorGroupsService, direction: if (context && typeof context.editorIndex === 'number') { editorToCopy = sourceGroup.getEditorByIndex(context.editorIndex); } else { - editorToCopy = withNullAsUndefined(sourceGroup.activeEditor); + editorToCopy = sourceGroup.activeEditor ?? undefined; } // Copy the editor to the new group, else create an empty group @@ -1461,7 +1461,7 @@ function resolveCommandsContext(editorGroupService: IEditorGroupsService, contex // Resolve from context let group = context && typeof context.groupId === 'number' ? editorGroupService.getGroup(context.groupId) : undefined; - let editor = group && context && typeof context.editorIndex === 'number' ? withNullAsUndefined(group.getEditorByIndex(context.editorIndex)) : undefined; + let editor = group && context && typeof context.editorIndex === 'number' ? group.getEditorByIndex(context.editorIndex) ?? undefined : undefined; // Fallback to active group as needed if (!group) { @@ -1470,7 +1470,7 @@ function resolveCommandsContext(editorGroupService: IEditorGroupsService, contex // Fallback to active editor as needed if (!editor) { - editor = withNullAsUndefined(group.activeEditor); + editor = group.activeEditor ?? undefined; } return { group, editor }; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 898bd528d24..c80fa632d3e 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -47,7 +47,6 @@ import { Schemas } from 'vs/base/common/network'; import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor'; import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { isLinux, isMacintosh, isNative, isWindows } from 'vs/base/common/platform'; @@ -831,7 +830,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } get activeEditorPane(): IVisibleEditorPane | undefined { - return this.editorPane ? withNullAsUndefined(this.editorPane.activeEditorPane) : undefined; + return this.editorPane ? this.editorPane.activeEditorPane ?? undefined : undefined; } get activeEditor(): EditorInput | null { @@ -1175,7 +1174,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Opening many editors at once can put any editor to be // the active one depending on options. As such, we simply // return the active editor pane after this operation. - return withNullAsUndefined(this.editorPane.activeEditorPane); + return this.editorPane.activeEditorPane ?? undefined; } //#endregion diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 4b8a497d2b1..d18e4fc12f7 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { format, compare, splitLines } from 'vs/base/common/strings'; import { extname, basename, isEqual } from 'vs/base/common/resources'; -import { areFunctions, assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; +import { areFunctions, assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; import { Language } from 'vs/base/common/platform'; @@ -579,7 +579,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private updateStatusBar(): void { const activeInput = this.editorService.activeEditor; const activeEditorPane = this.editorService.activeEditorPane; - const activeCodeEditor = activeEditorPane ? withNullAsUndefined(getCodeEditor(activeEditorPane.getControl())) : undefined; + const activeCodeEditor = activeEditorPane ? getCodeEditor(activeEditorPane.getControl()) ?? undefined : undefined; // Update all states this.onColumnSelectionModeChange(activeCodeEditor); @@ -686,7 +686,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { const textModel = editorWidget.getModel(); if (textModel) { const languageId = textModel.getLanguageId(); - info.languageId = withNullAsUndefined(this.languageService.getLanguageName(languageId)); + info.languageId = this.languageService.getLanguageName(languageId) ?? undefined; } } @@ -817,7 +817,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { if (activeEditorPane) { const activeResource = EditorResourceAccessor.getCanonicalUri(activeEditorPane.input, { supportSideBySide: SideBySideEditor.PRIMARY }); if (activeResource && isEqual(activeResource, resource)) { - const activeCodeEditor = withNullAsUndefined(getCodeEditor(activeEditorPane.getControl())); + const activeCodeEditor = getCodeEditor(activeEditorPane.getControl()) ?? undefined; return this.onEncodingChange(activeEditorPane, activeCodeEditor); // only update if the encoding changed for the active resource } @@ -1036,7 +1036,7 @@ export class ChangeLanguageAction extends Action2 { let currentLanguageId: string | undefined; if (textModel) { currentLanguageId = textModel.getLanguageId(); - currentLanguageName = withNullAsUndefined(languageService.getLanguageName(currentLanguageId)); + currentLanguageName = languageService.getLanguageName(currentLanguageId) ?? undefined; } let hasLanguageSupport = !!resource; @@ -1128,7 +1128,7 @@ export class ChangeLanguageAction extends Action2 { const resource = EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }); if (resource) { // Detect languages since we are in an untitled file - let languageId: string | undefined = withNullAsUndefined(languageService.guessLanguageIdByFilepathOrFirstLine(resource, textModel.getLineContent(1))); + let languageId: string | undefined = languageService.guessLanguageIdByFilepathOrFirstLine(resource, textModel.getLineContent(1)) ?? undefined; if (!languageId || languageId === 'unknown') { detectedLanguage = await languageDetectionService.detectLanguage(resource); languageId = detectedLanguage; @@ -1378,7 +1378,7 @@ export class ChangeEncodingAction extends Action2 { const isReopenWithEncoding = (action === reopenWithEncodingPick); - const configuredEncoding = textResourceConfigurationService.getValue(withNullAsUndefined(resource), 'files.encoding'); + const configuredEncoding = textResourceConfigurationService.getValue(resource ?? undefined, 'files.encoding'); let directMatchIndex: number | undefined; let aliasMatchIndex: number | undefined; diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 202c93594de..d60267cd22f 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -13,7 +13,7 @@ import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/brow import { addDisposableListener, EventType, EventHelper, Dimension, isAncestor } from 'vs/base/browser/dom'; import { CLOSE_EDITOR_COMMAND_ID, UNLOCK_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { Color } from 'vs/base/common/color'; -import { withNullAsUndefined, assertIsDefined, assertAllDefined } from 'vs/base/common/types'; +import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; import { IEditorGroupTitleHeight } from 'vs/workbench/browser/parts/editor/editor'; import { equals } from 'vs/base/common/objects'; import { toDisposable } from 'vs/base/common/lifecycle'; @@ -245,7 +245,7 @@ export class NoTabsTitleControl extends TitleControl { } private redraw(): void { - const editor = withNullAsUndefined(this.group.activeEditor); + const editor = this.group.activeEditor ?? undefined; const options = this.accessor.partOptions; const isEditorPinned = editor ? this.group.isPinned(editor) : false; diff --git a/src/vs/workbench/browser/parts/editor/textCodeEditor.ts b/src/vs/workbench/browser/parts/editor/textCodeEditor.ts index 6bc581eee8f..3cd1a486281 100644 --- a/src/vs/workbench/browser/parts/editor/textCodeEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textCodeEditor.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; +import { assertIsDefined } from 'vs/base/common/types'; import { ITextEditorPane } from 'vs/workbench/common/editor'; import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -77,7 +77,7 @@ export abstract class AbstractTextCodeEditor extends return undefined; // prevent saving view state for a model that is not the expected one } - return withNullAsUndefined(this.editorControl.saveViewState() as unknown as T); + return this.editorControl.saveViewState() as unknown as T ?? undefined; } override setOptions(options: ITextEditorOptions | undefined): void { diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 784b56215fe..091111019e2 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { deepClone } from 'vs/base/common/objects'; -import { isObject, assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; +import { isObject, assertIsDefined } from 'vs/base/common/types'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; import { AbstractTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; @@ -399,7 +399,7 @@ export class TextDiffEditor extends AbstractTextEditor imp return undefined; // prevent saving view state for a model that is not the expected one } - return withNullAsUndefined(this.diffEditorControl.saveViewState()); + return this.diffEditorControl.saveViewState() ?? undefined; } protected override toEditorViewStateResource(modelOrInput: IDiffEditorModel | EditorInput): URI | undefined { diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 6c88cf45b5f..2da5fb20fa3 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; +import { assertIsDefined } from 'vs/base/common/types'; import { ICodeEditor, IPasteEvent } from 'vs/editor/browser/editorBrowser'; import { IEditorOpenContext, isTextEditorViewState } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; @@ -195,7 +195,7 @@ export class TextResourceEditor extends AbstractTextResourceEditor { // We can still try to guess a good languageId from the first line if // the paste changed the first line else { - const guess = withNullAsUndefined(this.languageService.guessLanguageIdByFilepathOrFirstLine(textModel.uri, textModel.getLineContent(1).substr(0, ModelConstants.FIRST_LINE_DETECTION_LENGTH_LIMIT))); + const guess = this.languageService.guessLanguageIdByFilepathOrFirstLine(textModel.uri, textModel.getLineContent(1).substr(0, ModelConstants.FIRST_LINE_DETECTION_LENGTH_LIMIT)) ?? undefined; if (guess) { candidateLanguage = { id: guess, source: 'guess' }; } diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 0bfa78864d0..73a36af9470 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -33,7 +33,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ResourceContextKey, ActiveEditorPinnedContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, ActiveEditorAvailableEditorIdsContext, applyAvailableEditorIds } from 'vs/workbench/common/contextkeys'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IFileService } from 'vs/platform/files/common/files'; -import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; +import { assertIsDefined } from 'vs/base/common/types'; import { isFirefox } from 'vs/base/browser/browser'; import { isCancellationError } from 'vs/base/common/errors'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; @@ -418,7 +418,7 @@ export abstract class TitleControl extends Themable { protected getKeybindingLabel(action: IAction): string | undefined { const keybinding = this.getKeybinding(action); - return keybinding ? withNullAsUndefined(keybinding.getLabel()) : undefined; + return keybinding ? keybinding.getLabel() ?? undefined : undefined; } abstract openEditor(editor: EditorInput): void; diff --git a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts index bcfc971b2a4..4b5269d456e 100644 --- a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts +++ b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts @@ -22,7 +22,6 @@ import { Emitter } from 'vs/base/common/event'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtualWorkspace'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; @@ -205,7 +204,7 @@ export class WindowTitle extends Disposable { if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { folder = workspace.folders[0]; } else if (editorResource) { - folder = withNullAsUndefined(this.contextService.getWorkspaceFolder(editorResource)); + folder = this.contextService.getWorkspaceFolder(editorResource) ?? undefined; } // Compute remote diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index 03aa9873c02..76248f4429f 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -11,7 +11,6 @@ import { TEXT_DIFF_EDITOR_ID, BINARY_DIFF_EDITOR_ID, Verbosity, IEditorDescripto import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { DiffEditorModel } from 'vs/workbench/common/editor/diffEditorModel'; import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { shorten } from 'vs/base/common/labels'; @@ -201,7 +200,7 @@ export class DiffEditorInput extends SideBySideEditorInput implements IDiffEdito } // Otherwise return normal diff model - return new DiffEditorModel(withNullAsUndefined(originalEditorModel), withNullAsUndefined(modifiedEditorModel)); + return new DiffEditorModel(originalEditorModel ?? undefined, modifiedEditorModel ?? undefined); } override toUntyped(options?: { preserveViewState: GroupIdentifier }): (IResourceDiffEditorInput & IResourceSideBySideEditorInput) | undefined { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 9fe45ef64a3..5c59b07b1c4 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -6,7 +6,6 @@ import { localize } from 'vs/nls'; import { format } from 'vs/base/common/strings'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; @@ -65,7 +64,7 @@ export async function runAccessibilityHelpAction(accessor: ServicesAccessor, edi if (!inputEditor || !editorUri) { return; } - const domNode = withNullAsUndefined(inputEditor.getDomNode()); + const domNode = inputEditor.getDomNode() ?? undefined; if (!domNode) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 146af7fe6e5..ce72542854c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -25,7 +25,6 @@ import { ResourceMap } from 'vs/base/common/map'; import { marked } from 'vs/base/common/marked/marked'; import { FileAccess } from 'vs/base/common/network'; import { ThemeIcon } from 'vs/base/common/themables'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -807,7 +806,7 @@ class CodeBlockPart extends Disposable implements IChatResultCodeBlockPart { const text = this.fixCodeText(data.text, data.languageId); this.setText(text); - const vscodeLanguageId = withNullAsUndefined(this.languageService.getLanguageIdByLanguageName(data.languageId)); + const vscodeLanguageId = this.languageService.getLanguageIdByLanguageName(data.languageId) ?? undefined; this.setLanguage(vscodeLanguageId); this.layout(width); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 24ca69d9d1c..c0a162bfdf1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -10,7 +10,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/chat'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -411,7 +410,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } getFocus(): ChatTreeItem | undefined { - return withNullAsUndefined(this.tree.getFocus()[0]); + return this.tree.getFocus()[0] ?? undefined; } reveal(item: ChatTreeItem): void { diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index c7008c19e8a..9956ea633ca 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -11,7 +11,6 @@ import { Iterable } from 'vs/base/common/iterator'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; import { StopWatch } from 'vs/base/common/stopwatch'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -351,7 +350,7 @@ export class ChatService extends Disposable implements IChatService { let session: IChat | undefined; try { - session = withNullAsUndefined(await provider.prepareSession(model.providerState, token)); + session = await provider.prepareSession(model.providerState, token) ?? undefined; } catch (err) { this.trace('initializeSession', `Provider initializeSession threw: ${err}`); } @@ -362,7 +361,7 @@ export class ChatService extends Disposable implements IChatService { this.trace('startSession', `Provider returned session`); - const welcomeMessage = model.welcomeMessage ? undefined : withNullAsUndefined(await provider.provideWelcomeMessage?.(token)); + const welcomeMessage = model.welcomeMessage ? undefined : await provider.provideWelcomeMessage?.(token) ?? undefined; const welcomeModel = welcomeMessage && new ChatWelcomeMessageModel( model, welcomeMessage.map(item => typeof item === 'string' ? new MarkdownString(item) : item as IChatReplyFollowup[])); @@ -500,7 +499,7 @@ export class ChatService extends Disposable implements IChatService { // TODO refactor this or rethink the API https://github.com/microsoft/vscode-copilot/issues/593 if (provider.provideFollowups) { Promise.resolve(provider.provideFollowups(model.session!, CancellationToken.None)).then(followups => { - model.setFollowups(request, withNullAsUndefined(followups)); + model.setFollowups(request, followups ?? undefined); model.completeResponse(request); }); } else { @@ -568,12 +567,12 @@ export class ChatService extends Disposable implements IChatService { try { const slashCommands = (await providerResults).filter(c => !!c) as ISlashCommand[][]; - return withNullAsUndefined(slashCommands.flat()); + return slashCommands.flat() ?? undefined; } catch (e) { this.logService.error(e); // If one of the other contributed providers fails, return the main provider's result - return withNullAsUndefined(await mainProviderRequest); + return await mainProviderRequest ?? undefined; } } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 6301479ad72..1f00b285fa2 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -7,7 +7,6 @@ import * as dom from 'vs/base/browser/dom'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; @@ -171,7 +170,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } if (this._commentGlyph) { - return withNullAsUndefined(this._commentGlyph.getPosition().position); + return this._commentGlyph.getPosition().position ?? undefined; } return undefined; } diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index 97df0066099..339b10df9e7 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -37,7 +37,6 @@ import { Position } from 'vs/editor/common/core/position'; import { CommentThreadRangeDecorator } from 'vs/workbench/contrib/comments/browser/commentThreadRangeDecorator'; import { ICursorSelectionChangedEvent } from 'vs/editor/common/cursorEvents'; import { CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsView'; -import { withNullAsUndefined } from 'vs/base/common/types'; export const ID = 'editor.contrib.review'; @@ -498,7 +497,7 @@ export class CommentController implements IEditorContribution { }).then(commentInfos => { if (this.commentService.isCommentingEnabled) { const meaningfulCommentInfos = coalesce(commentInfos); - this._commentingRangeDecorator.update(this.editor, meaningfulCommentInfos, this.editor?.getPosition()?.lineNumber, withNullAsUndefined(this.editor?.getSelection())); + this._commentingRangeDecorator.update(this.editor, meaningfulCommentInfos, this.editor?.getPosition()?.lineNumber, this.editor?.getSelection() ?? undefined); } }, (err) => { onUnexpectedError(err); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 8c7e0c62b0e..c6126663925 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -18,7 +18,6 @@ import * as env from 'vs/base/common/platform'; import severity from 'vs/base/common/severity'; import { noBreakWhitespace } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/base/common/themables'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { ContentWidgetPositionPreference, IActiveCodeEditor, ICodeEditor, IContentWidget, IContentWidgetPosition, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; @@ -95,7 +94,7 @@ function getBreakpointDecorationOptions(accessor: ServicesAccessor, model: IText if (message) { if (!langId) { // Lazily compute this, only if needed for some debug adapter - langId = withNullAsUndefined(languageService.guessLanguageIdByFilepathOrFirstLine(breakpoint.uri)); + langId = languageService.guessLanguageIdByFilepathOrFirstLine(breakpoint.uri) ?? undefined; } return langId && dbg.interestedInLanguage(langId) ? message : undefined; } diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts index 44e073d3d77..0433b7ae76b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -11,7 +11,6 @@ import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { matchesFuzzy } from 'vs/base/common/filters'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { ADD_CONFIGURATION_ID, DEBUG_QUICK_ACCESS_PREFIX } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { debugConfigure, debugRemoveConfig } from 'vs/workbench/contrib/debug/browser/debugIcons'; import { ThemeIcon } from 'vs/base/common/themables'; @@ -151,7 +150,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider this.commandService.executeCommand(ADD_CONFIGURATION_ID, launch.uri.toString()) }); } diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts index 3d763f0c453..9ebea229e18 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts @@ -7,7 +7,6 @@ import { Emitter } from 'vs/base/common/event'; import { combinedDisposable, Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; import { isEqual } from 'vs/base/common/resources'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; @@ -85,7 +84,7 @@ export class NotebookExecutionStateService extends Disposable implements INotebo getCellExecutionsByHandleForNotebook(notebook: URI): Map | undefined { const exeMap = this._executions.get(notebook); - return withNullAsUndefined(exeMap); + return exeMap ?? undefined; } private _onCellExecutionDidChange(notebookUri: URI, cellHandle: number, exe: CellExecution): void { diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts index c8e4a1fb3c0..894b9e8f74d 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts @@ -23,7 +23,6 @@ import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/b import { asCssVariable, editorWidgetBackground, editorWidgetForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { SearchWidget, SearchOptions } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { Promises, timeout } from 'vs/base/common/async'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { defaultInputBoxStyles, defaultKeybindingLabelStyles } from 'vs/platform/theme/browser/defaultStyles'; @@ -244,7 +243,7 @@ export class DefineKeybindingWidget extends Widget { dom.clearNode(this._showExistingKeybindingsNode); const firstLabel = new KeybindingLabel(this._outputNode, OS, defaultKeybindingLabelStyles); - firstLabel.set(withNullAsUndefined(this._chords?.[0])); + firstLabel.set(this._chords?.[0] ?? undefined); if (this._chords) { for (let i = 1; i < this._chords.length; i++) { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 315476bf3be..98dfa564221 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -19,7 +19,6 @@ import { Iterable } from 'vs/base/common/iterator'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/settingsEditor2'; import { localize } from 'vs/nls'; @@ -839,7 +838,7 @@ export class SettingsEditor2 extends EditorPane { this.tocTree.setSelection(element ? [element] : []); if (this.searchResultModel) { if (this.viewState.filterToCategory !== element) { - this.viewState.filterToCategory = withNullAsUndefined(element); + this.viewState.filterToCategory = element ?? undefined; // Force render in this case, because // onDidClickSetting relies on the updated view. this.renderTree(undefined, true); diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index 78059b2f79a..66c38b0fb91 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -13,7 +13,6 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { PaneCompositeDescriptor } from 'vs/workbench/browser/panecomposite'; import { matchesFuzzy } from 'vs/base/common/filters'; import { fuzzyContains } from 'vs/base/common/strings'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Action2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -56,7 +55,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider number { @@ -71,7 +70,7 @@ export class QuickDiffService extends Disposable implements IQuickDiffService { const diffs = await Promise.all(providers.map(async provider => { const scoreValue = provider.selector ? score(provider.selector, uri, language, isSynchronized, undefined, undefined) : 10; const diff: Partial = { - originalResource: scoreValue > 0 ? withNullAsUndefined(await provider.getOriginalResource(uri)) : undefined, + originalResource: scoreValue > 0 ? await provider.getOriginalResource(uri) ?? undefined : undefined, label: provider.label, isSCM: provider.isSCM }; diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index dde4d2279da..ccd7471df75 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -48,7 +48,6 @@ import { ScrollType, IEditor, ICodeEditorViewState, IDiffEditorViewState } from import { once } from 'vs/base/common/functional'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { getIEditor } from 'vs/editor/browser/editorBrowser'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; @@ -145,7 +144,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider const options: ITextQueryBuilderOptions = { _reason: 'searchEditor', extraFileResources: this.instantiationService.invokeFunction(getOutOfWorkspaceEditorResources), - maxResults: withNullAsUndefined(this.searchConfig.maxResults), + maxResults: this.searchConfig.maxResults ?? undefined, disregardIgnoreFiles: !config.useExcludeSettingsAndIgnoreFiles || undefined, disregardExcludeSettings: !config.useExcludeSettingsAndIgnoreFiles || undefined, excludePattern: config.filesToExclude, diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index 5ffd22ec0d3..787d4cd991e 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { Schemas } from 'vs/base/common/network'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchEditor'; import { ICodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; @@ -108,7 +107,7 @@ export const openNewSearchEditor = const workspaceContextService = accessor.get(IWorkspaceContextService); const historyService = accessor.get(IHistoryService); const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(Schemas.file); - const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? withNullAsUndefined(workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined; + const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined; const activeEditorControl = editorService.activeTextEditorControl; diff --git a/src/vs/workbench/contrib/terminal/browser/baseTerminalBackend.ts b/src/vs/workbench/contrib/terminal/browser/baseTerminalBackend.ts index 0cec710ed1e..fcb01ae3e84 100644 --- a/src/vs/workbench/contrib/terminal/browser/baseTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/browser/baseTerminalBackend.ts @@ -6,7 +6,6 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { localize } from 'vs/nls'; import { ICrossVersionSerializedTerminalState, IPtyHostController, ISerializedTerminalState, ITerminalLogService } from 'vs/platform/terminal/common/terminal'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; @@ -92,7 +91,7 @@ export abstract class BaseTerminalBackend extends Disposable { return; } const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(Schemas.file); - const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? withNullAsUndefined(this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined; + const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined; const resolveCalls: Promise[] = e.originalText.map(t => { return configurationResolverService.resolveAsync(lastActiveWorkspaceRoot, t); }); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 5932d72db82..88e8def8389 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -10,7 +10,7 @@ import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Schemas } from 'vs/base/common/network'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { withNullAsUndefined, isObject, isString } from 'vs/base/common/types'; +import { isObject, isString } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { EndOfLinePreference } from 'vs/editor/common/model'; @@ -112,7 +112,7 @@ export const terminalSendSequenceCommand = async (accessor: ServicesAccessor, ar const workspaceContextService = accessor.get(IWorkspaceContextService); const historyService = accessor.get(IHistoryService); const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(instance.isRemote ? Schemas.vscodeRemote : Schemas.file); - const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? withNullAsUndefined(workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined; + const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined; const resolvedText = await configurationResolverService.resolveAsync(lastActiveWorkspaceRoot, text); instance.sendText(resolvedText, false); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 3d65f2cf44b..fecc0535f37 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -22,7 +22,6 @@ import { Schemas } from 'vs/base/common/network'; import * as path from 'vs/base/common/path'; import { OS, OperatingSystem, isMacintosh, isWindows } from 'vs/base/common/platform'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { TabFocus, TabFocusContext } from 'vs/editor/browser/config/tabFocus'; import * as nls from 'vs/nls'; @@ -392,12 +391,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { path: this._shellLaunchConfig.cwd }) : this._shellLaunchConfig.cwd; if (cwdUri) { - this._workspaceFolder = withNullAsUndefined(this._workspaceContextService.getWorkspaceFolder(cwdUri)); + this._workspaceFolder = this._workspaceContextService.getWorkspaceFolder(cwdUri) ?? undefined; } } if (!this._workspaceFolder) { const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); - this._workspaceFolder = activeWorkspaceRootUri ? withNullAsUndefined(this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined; + this._workspaceFolder = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined; } const scopedContextKeyService = this._register(_contextKeyService.createScoped(this._wrapperElement)); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts index 1b0aa408a81..f61f13cb486 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts @@ -5,7 +5,6 @@ import { Schemas } from 'vs/base/common/network'; import { env } from 'vs/base/common/process'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; @@ -297,7 +296,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro // Resolve path variables const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(options.remoteAuthority ? Schemas.vscodeRemote : Schemas.file); - const lastActiveWorkspace = activeWorkspaceRootUri ? withNullAsUndefined(this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined; + const lastActiveWorkspace = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined; profile.path = await this._resolveVariables(profile.path, env, lastActiveWorkspace); // Resolve args variables diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index c1a38a3f014..04ed7f22ce7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -37,7 +37,6 @@ import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { getTerminalActionBarArgs } from 'vs/workbench/contrib/terminal/browser/terminalMenus'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { getInstanceHoverInfo } from 'vs/workbench/contrib/terminal/browser/terminalTooltip'; @@ -294,7 +293,7 @@ export class TerminalViewPane extends ViewPane { } private _getKeybindingLabel(action: IAction): string | undefined { - return withNullAsUndefined(this._keybindingService.lookupKeybinding(action.id)?.getLabel()); + return this._keybindingService.lookupKeybinding(action.id)?.getLabel() ?? undefined; } private _updateTabActionBar(profiles: ITerminalProfile[]): void { diff --git a/src/vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel.ts index 1e493eecc6f..ab1c7d898c7 100644 --- a/src/vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; @@ -133,7 +132,7 @@ export class RemoteTerminalChannelClient implements IPtyHostController { // We will use the resolver service to resolve all the variables in the config / launch config // But then we will keep only some variables, since the rest need to be resolved on the remote side const resolvedVariables = Object.create(null); - const lastActiveWorkspace = activeWorkspaceRootUri ? withNullAsUndefined(this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined; + const lastActiveWorkspace = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined; let allResolvedVariables: Map | undefined = undefined; try { allResolvedVariables = (await this._resolverService.resolveAnyMap(lastActiveWorkspace, { diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index db80c0b1a3a..22aedd0758c 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -15,7 +15,7 @@ import { sanitizeProcessEnvironment } from 'vs/base/common/processes'; import { IShellLaunchConfig, ITerminalBackend, ITerminalEnvironment, TerminalShellType, WindowsShellType } from 'vs/platform/terminal/common/terminal'; import { IProcessEnvironment, isWindows, language, OperatingSystem } from 'vs/base/common/platform'; import { escapeNonWindowsPath, sanitizeCwd } from 'vs/platform/terminal/common/terminalEnvironment'; -import { isString, withNullAsUndefined } from 'vs/base/common/types'; +import { isString } from 'vs/base/common/types'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { ILogService } from 'vs/platform/log/common/log'; @@ -361,12 +361,12 @@ export async function preparePathForShell(resource: string | URI, executable: st export function getWorkspaceForTerminal(cwd: URI | string | undefined, workspaceContextService: IWorkspaceContextService, historyService: IHistoryService): IWorkspaceFolder | undefined { const cwdUri = typeof cwd === 'string' ? URI.parse(cwd) : cwd; - let workspaceFolder = cwdUri ? withNullAsUndefined(workspaceContextService.getWorkspaceFolder(cwdUri)) : undefined; + let workspaceFolder = cwdUri ? workspaceContextService.getWorkspaceFolder(cwdUri) ?? undefined : undefined; if (!workspaceFolder) { // fallback to last active workspace if cwd is not available or it is not in workspace // TOOD: last active workspace is known to be unreliable, we should remove this fallback eventually const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(); - workspaceFolder = activeWorkspaceRootUri ? withNullAsUndefined(workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined; + workspaceFolder = activeWorkspaceRootUri ? workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined; } return workspaceFolder; } diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts index bb730ffa8ac..d2a375a498f 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts @@ -5,7 +5,6 @@ import { Emitter } from 'vs/base/common/event'; import { IProcessEnvironment, isMacintosh, isWindows, OperatingSystem } from 'vs/base/common/platform'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -331,7 +330,7 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke try { // Create variable resolver const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); - const lastActiveWorkspace = activeWorkspaceRootUri ? withNullAsUndefined(this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined; + const lastActiveWorkspace = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined; const variableResolver = terminalEnvironment.createVariableResolver(lastActiveWorkspace, await this._terminalProfileResolverService.getEnvironment(this.remoteAuthority), this._configurationResolverService); // Re-resolve the environments and replace it on the state so local terminals use a fresh diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts index 18a05b903cf..fab258f94e6 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; @@ -121,7 +120,7 @@ export class AccessibleBufferWidget extends TerminalAccessibleWidget { } async createQuickPick(): Promise | undefined> { - this._cursorPosition = withNullAsUndefined(this.editorWidget.getPosition()); + this._cursorPosition = this.editorWidget.getPosition() ?? undefined; const commands = this._getCommandsWithEditorLine(); if (!commands) { return; @@ -233,7 +232,7 @@ export class AccessibleBufferWidget extends TerminalAccessibleWidget { // Save the view state before the update if it was set by the user let savedViewState: IEditorViewState | undefined; if (dataChanged) { - savedViewState = withNullAsUndefined(this.editorWidget.saveViewState()); + savedViewState = this.editorWidget.saveViewState() ?? undefined; } let model = this.editorWidget.getModel(); diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts index 313cda95762..b56367f4cb6 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts @@ -7,7 +7,6 @@ import { addDisposableListener, Dimension, EventType, findParentWithClass } from import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -301,6 +300,6 @@ export class WebviewViewPane extends ViewPane { } private findRootContainer(container: HTMLElement): HTMLElement | undefined { - return withNullAsUndefined(findParentWithClass(container, 'monaco-scrollable-element')); + return findParentWithClass(container, 'monaco-scrollable-element') ?? undefined; } } diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts index a5d93553f0b..be89833271e 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts @@ -19,7 +19,6 @@ import { IOpenRecentAction, MenubarControl } from 'vs/workbench/browser/parts/ti import { IStorageService } from 'vs/platform/storage/common/storage'; import { IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenubarMenuItemSubmenu, IMenubarMenuItemAction, MenubarMenuItem } from 'vs/platform/menubar/common/menubar'; import { IMenubarService } from 'vs/platform/menubar/electron-sandbox/menubar'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { INativeHostService } from 'vs/platform/native/common/native'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; @@ -197,13 +196,13 @@ export class NativeMenubarControl extends MenubarControl { // first try to resolve a native accelerator const electronAccelerator = binding.getElectronAccelerator(); if (electronAccelerator) { - return { label: electronAccelerator, userSettingsLabel: withNullAsUndefined(binding.getUserSettingsLabel()) }; + return { label: electronAccelerator, userSettingsLabel: binding.getUserSettingsLabel() ?? undefined }; } // we need this fallback to support keybindings that cannot show in electron menus (e.g. chords) const acceleratorLabel = binding.getLabel(); if (acceleratorLabel) { - return { label: acceleratorLabel, isNative: false, userSettingsLabel: withNullAsUndefined(binding.getUserSettingsLabel()) }; + return { label: acceleratorLabel, isNative: false, userSettingsLabel: binding.getUserSettingsLabel() ?? undefined }; } return undefined; diff --git a/src/vs/workbench/services/configuration/common/configurationEditing.ts b/src/vs/workbench/services/configuration/common/configurationEditing.ts index 2d42e3db59c..721f789dadb 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditing.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditing.ts @@ -20,7 +20,6 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenSettingsOptions, IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { ITextModel } from 'vs/editor/common/model'; import { IReference } from 'vs/base/common/lifecycle'; @@ -569,14 +568,14 @@ export class ConfigurationEditing { // Check for prefix if (config.key === key) { const jsonPath = this.isWorkspaceConfigurationResource(resource) ? [key] : []; - return { key: jsonPath[jsonPath.length - 1], jsonPath, value: config.value, resource: withNullAsUndefined(resource), workspaceStandAloneConfigurationKey: key, target }; + return { key: jsonPath[jsonPath.length - 1], jsonPath, value: config.value, resource: resource ?? undefined, workspaceStandAloneConfigurationKey: key, target }; } // Check for prefix. const keyPrefix = `${key}.`; if (config.key.indexOf(keyPrefix) === 0) { const jsonPath = this.isWorkspaceConfigurationResource(resource) ? [key, config.key.substr(keyPrefix.length)] : [config.key.substr(keyPrefix.length)]; - return { key: jsonPath[jsonPath.length - 1], jsonPath, value: config.value, resource: withNullAsUndefined(resource), workspaceStandAloneConfigurationKey: key, target }; + return { key: jsonPath[jsonPath.length - 1], jsonPath, value: config.value, resource: resource ?? undefined, workspaceStandAloneConfigurationKey: key, target }; } } } @@ -586,14 +585,14 @@ export class ConfigurationEditing { const configurationScope = configurationProperties[key]?.scope; let jsonPath = overrides.overrideIdentifiers?.length ? [keyFromOverrideIdentifiers(overrides.overrideIdentifiers), key] : [key]; if (target === EditableConfigurationTarget.USER_LOCAL || target === EditableConfigurationTarget.USER_REMOTE) { - return { key, jsonPath, value: config.value, resource: withNullAsUndefined(this.getConfigurationFileResource(target, key, '', null, configurationScope)), target }; + return { key, jsonPath, value: config.value, resource: this.getConfigurationFileResource(target, key, '', null, configurationScope) ?? undefined, target }; } const resource = this.getConfigurationFileResource(target, key, FOLDER_SETTINGS_PATH, overrides.resource, configurationScope); if (this.isWorkspaceConfigurationResource(resource)) { jsonPath = ['settings', ...jsonPath]; } - return { key, jsonPath, value: config.value, resource: withNullAsUndefined(resource), target }; + return { key, jsonPath, value: config.value, resource: resource ?? undefined, target }; } private isWorkspaceConfigurationResource(resource: URI | null): boolean { diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index ad7d341a7b8..e241e493f65 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -22,7 +22,7 @@ import { coalesce, distinct } from 'vs/base/common/arrays'; import { isCodeEditor, isDiffEditor, ICodeEditor, IDiffEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorGroupView, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { isUndefined, withNullAsUndefined } from 'vs/base/common/types'; +import { isUndefined } from 'vs/base/common/types'; import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver'; import { Promises, timeout } from 'vs/base/common/async'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -136,7 +136,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Remember as last active const activeGroup = this.editorGroupService.activeGroup; - this.lastActiveEditor = withNullAsUndefined(activeGroup.activeEditor); + this.lastActiveEditor = activeGroup.activeEditor ?? undefined; // Fire event to outside parties this._onDidActiveEditorChange.fire(); @@ -459,7 +459,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { get activeEditor(): EditorInput | undefined { const activeGroup = this.editorGroupService.activeGroup; - return activeGroup ? withNullAsUndefined(activeGroup.activeEditor) : undefined; + return activeGroup ? activeGroup.activeEditor ?? undefined : undefined; } get visibleEditorPanes(): IVisibleEditorPane[] { diff --git a/src/vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler.ts b/src/vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler.ts index 12671cf7cce..8b380b9a887 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler.ts @@ -6,7 +6,6 @@ import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { IExtensionHostProfile, IExtensionService, ProfileSegmentId, ProfileSession } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { IV8InspectProfilingService, IV8Profile, IV8ProfileNode } from 'vs/platform/profiling/common/profiling'; @@ -104,7 +103,7 @@ export class ExtensionHostProfiler { distilledIds.push(currSegmentId); distilledDeltas.push(currSegmentTime); } - currSegmentId = withNullAsUndefined(segmentId); + currSegmentId = segmentId ?? undefined; currSegmentTime = 0; } currSegmentTime += timeDeltas[i]; diff --git a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts index 763d67ddf37..b72e19b9ec6 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts @@ -12,7 +12,6 @@ import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { removeDangerousEnvVariables } from 'vs/base/common/processes'; import { StopWatch } from 'vs/base/common/stopwatch'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; @@ -440,7 +439,7 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost { extensionLogLevel: this._environmentService.extensionLogLevel }, workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : { - configuration: withNullAsUndefined(workspace.configuration), + configuration: workspace.configuration ?? undefined, id: workspace.id, name: this._labelService.getWorkspaceLabel(workspace), isUntitled: workspace.configuration ? isUntitledWorkspace(workspace.configuration, this._environmentService) : false, @@ -533,7 +532,7 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost { } public getInspectPort(): number | undefined { - return withNullAsUndefined(this._inspectPort); + return this._inspectPort ?? undefined; } private _onWillShutdown(event: WillShutdownEvent): void { diff --git a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts index 19d723d383a..9e9594a25f4 100644 --- a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts +++ b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts @@ -20,7 +20,6 @@ import { IdleValue } from 'vs/base/common/async'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ResourceMap } from 'vs/base/common/map'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { IMarkdownString } from 'vs/base/common/htmlContent'; export const AutoSaveAfterShortDelayContext = new RawContextKey('autoSaveAfterShortDelayContext', false, true); @@ -168,7 +167,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi if ( this.uriIdentityService.extUri.isEqualOrParent(resource, this.environmentService.userRoamingDataHome) || - this.uriIdentityService.extUri.isEqual(resource, withNullAsUndefined(this.contextService.getWorkspace().configuration)) + this.uriIdentityService.extUri.isEqual(resource, this.contextService.getWorkspace().configuration ?? undefined) ) { return false; // explicitly exclude some paths from readonly that we need for configuration } diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 8b374aa28ea..0ccee385532 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -21,7 +21,6 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { IFilterMetadata, IFilterResult, IGroupFilter, IKeybindingsEditorModel, ISearchResultGroup, ISetting, ISettingMatch, ISettingMatcher, ISettingsEditorModel, ISettingsGroup, SettingMatchType } from 'vs/workbench/services/preferences/common/preferences'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { createValidator } from 'vs/workbench/services/preferences/common/preferencesValidation'; @@ -387,7 +386,7 @@ function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, valueRange: nullRange, descriptionRanges: [], overrides: [], - overrideOf: withNullAsUndefined(overrideSetting) + overrideOf: overrideSetting ?? undefined, }; if (previousParents.length === settingsPropertyIndex + 1) { settings.push(setting); diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index d2efcbdad27..45015baa458 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -43,7 +43,6 @@ import { IDecorationData, IDecorationsProvider, IDecorationsService } from 'vs/w import { Emitter } from 'vs/base/common/event'; import { Codicon } from 'vs/base/common/codicons'; import { listErrorForeground } from 'vs/platform/theme/common/colorRegistry'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { firstOrDefault } from 'vs/base/common/arrays'; /** @@ -308,7 +307,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex acceptTextOnly: options?.acceptTextOnly ?? false, guessEncoding: options?.autoGuessEncoding || this.textResourceConfigurationService.getValue(resource, 'files.autoGuessEncoding'), overwriteEncoding: async detectedEncoding => { - const { encoding } = await this.encoding.getPreferredReadEncoding(resource, options, withNullAsUndefined(detectedEncoding)); + const { encoding } = await this.encoding.getPreferredReadEncoding(resource, options, detectedEncoding ?? undefined); return encoding; } @@ -507,7 +506,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex let sourceTextModel: ITextModel | undefined = undefined; if (sourceModel instanceof BaseTextEditorModel) { if (sourceModel.isResolved()) { - sourceTextModel = withNullAsUndefined(sourceModel.textEditorModel); + sourceTextModel = sourceModel.textEditorModel ?? undefined; } } else { sourceTextModel = sourceModel as ITextModel; diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index e5df1583fac..51af0329394 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { mark } from 'vs/base/common/performance'; -import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; +import { assertIsDefined } from 'vs/base/common/types'; import { EncodingMode, ITextFileService, TextFileEditorModelState, ITextFileEditorModel, ITextFileStreamContent, ITextFileResolveOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions, TextFileResolveReason, ITextFileEditorModelSaveEvent } from 'vs/workbench/services/textfile/common/textfiles'; import { IRevertOptions, SaveReason, SaveSourceRegistry } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; @@ -227,7 +227,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Fill in content the same way we would do when // saving the file via the text file service // encoding support (hardcode UTF-8) - const content = await this.textFileService.getEncodedReadable(this.resource, withNullAsUndefined(this.createSnapshot()), { encoding: UTF8 }); + const content = await this.textFileService.getEncodedReadable(this.resource, this.createSnapshot() ?? undefined, { encoding: UTF8 }); return { meta, content }; } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts index ac48868bd37..93d906d886e 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts @@ -18,7 +18,7 @@ import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/wo import { IWorkingCopy, WorkingCopyCapabilities, IWorkingCopyBackup, NO_TYPE_ID, IWorkingCopySaveEvent } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { IEncodingSupport, ILanguageSupport, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents'; -import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; +import { assertIsDefined } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; import { ensureValidWordDefinition } from 'vs/editor/common/core/wordHelper'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -295,7 +295,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt if (this.isResolved()) { // Fill in content the same way we would do when saving the file // via the text file service encoding support (hardcode UTF-8) - content = await this.textFileService.getEncodedReadable(this.resource, withNullAsUndefined(this.createSnapshot()), { encoding: UTF8 }); + content = await this.textFileService.getEncodedReadable(this.resource, this.createSnapshot() ?? undefined, { encoding: UTF8 }); } else if (typeof this.initialValue === 'string') { content = bufferToReadable(VSBuffer.fromString(this.initialValue)); } From e9018d58c9464148bc5708c9ac43bd803101f1ec Mon Sep 17 00:00:00 2001 From: Dave Dash Date: Tue, 8 Aug 2023 16:42:58 -0700 Subject: [PATCH 32/69] Fish integration: Use -- in string to signify beginning of positional arguments In some cases in VSCode + fish you can run a command like: ``` echo 'hi -- hi' ``` It's perfectly legitimate and will run in a normal terminal just fine. However VSCode's shell extensions will raise an error: ``` string join: -- hi': unknown option /Applications/Visual Studio Code.app/Contents/Resources/app/out/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish (line 1): string join ";" $argv ^ in command substitution called on line 65 of file /Applications/Visual Studio Code.app/Contents/Resources/app/out/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish in function '__vsc_esc' with arguments 'E echo\ \'hi "" --\ hi\'' called on line 72 of file /Applications/Visual Studio Code.app/Contents/Resources/app/out/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish in function '__vsc_cmd_executed' with arguments 'echo\ \'hi\n\n--\ hi\'' in event handler: handler for generic event 'fish_preexec' (Type 'help string' for related documentation) ``` The problem is `string` interprets the `--hi` as a switch. By adding `--` we can negate that behavior and let `--hi` be treated as a positional argument. See the [string documentation][s] > Arguments beginning with - are normally interpreted as switches; -- causes the following arguments not to be treated as switches even if they begin with -. Switches and required arguments are recognized only on the command line. [s]: https://fishshell.com/docs/current/cmds/string.html --- .../fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish b/src/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish index 591e337db9c..d395be291de 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish +++ b/src/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish @@ -62,7 +62,7 @@ end # Helper function function __vsc_esc -d "Emit escape sequences for VS Code shell integration" - builtin printf "\e]633;%s\a" (string join ";" $argv) + builtin printf "\e]633;%s\a" (string join ";" -- $argv) end # Sent right before executing an interactive command. From bf9c8105647b1e9d244f0f00155c21298e7554cf Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 8 Aug 2023 17:24:35 -0700 Subject: [PATCH 33/69] testing: improve experience for test-correlated console output (#189993) Previously, console messages associated with a test were shown in the Test Results tree view, and clicking them opened a simple text editor stripped of ANSI codes ![](https://memes.peet.io/img/23-08-ca47357c-e9e6-49fb-bb60-80a65e49af6e.png) This was not a great experience, and caused some problems. Instead, we now have a "Show Output" message for each test with output in a tree view, which opens terminal output from _just_ that test case. ![](https://memes.peet.io/img/23-08-c3e00702-4f56-4974-9a98-cedcdf26179f.png) > Input: would selecting the test's range from the entire output be a better experience? Like the full results terminal, this will stream in data as the test runs. Additionally, clicking inline test messages now opens a real terminal with that message selected. ![](https://memes.peet.io/img/23-08-dc4e3b0d-d85c-44ce-abe0-168ce11e8220.png) cc @Tyriar regarding the new method in IXtermTerminal Closes #187104 (supplanting the pain point with better ux) Found a couple quirks with the DiffEditorWidget2 that I didn't catch in the initial `@deprecated`-triggered migration in debt week (#189992, #189975), please bear in mind during testing that these are separate issues. --- .../common/xterm/shellIntegrationAddon.ts | 4 + .../contrib/terminal/browser/terminal.ts | 16 +- .../terminal/browser/xterm/xtermTerminal.ts | 22 +- .../testing/browser/testingOutputPeek.ts | 263 ++++++++++++++---- .../contrib/testing/common/testResult.ts | 95 +++++-- .../testing/common/testingContentProvider.ts | 10 + .../contrib/testing/common/testingUri.ts | 58 ++-- .../test/common/testResultService.test.ts | 6 +- 8 files changed, 370 insertions(+), 104 deletions(-) diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index 16a760d57b1..dd97f30b522 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -236,6 +236,10 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati this._ensureCapabilitiesOrAddFailureTelemetry(); } + getMarkerId(terminal: Terminal, vscodeMarkerId: string) { + this._createOrGetBufferMarkDetection(terminal).getMark(vscodeMarkerId); + } + private _handleFinalTermSequence(data: string): boolean { const didHandle = this._doHandleFinalTermSequence(data); if (this._status === ShellIntegrationStatus.Off) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index ec146c02cf6..0bc652b97d5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -1040,6 +1040,17 @@ export interface IXtermTerminal extends IDisposable { */ selectAll(): void; + /** + * Selects the content between the two markers by their VS Code OSC `SetMarker` + * ID. It's a no-op if either of the two markers are not found. + * + * @param fromMarkerId Start marker ID + * @param toMarkerId End marker ID + * @param scrollIntoView Whether the terminal should scroll to the start of + * the range, defaults tof alse + */ + selectMarkedRange(fromMarkerId: string, toMarkerId: string, scrollIntoView?: boolean): void; + /** * Copies the terminal selection. * @param {boolean} copyAsHtml Whether to copy selection as HTML, defaults to false. @@ -1096,8 +1107,11 @@ export interface IDetachedXtermTerminal extends IXtermTerminal { /** * Writes data to the terminal. + * @param data data to write + * @param callback Optional callback that fires when the data was processed + * by the parser. */ - write(data: string | Uint8Array): void; + write(data: string | Uint8Array, callback?: () => void): void; /** * Resizes the terminal. diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 8568498af91..f3776f4eab8 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -381,8 +381,8 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, ID this._anyFocusedTerminalHasSelection.set(isFocused && this.raw.hasSelection()); } - write(data: string | Uint8Array): void { - this.raw.write(data); + write(data: string | Uint8Array, callback?: () => void): void { + this.raw.write(data, callback); } resize(columns: number, rows: number): void { @@ -595,6 +595,24 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, ID this.raw.clearSelection(); } + selectMarkedRange(fromMarkerId: string, toMarkerId: string, scrollIntoView = false) { + const detectionCapability = this.shellIntegration.capabilities.get(TerminalCapability.BufferMarkDetection); + if (!detectionCapability) { + return; + } + + const start = detectionCapability.getMark(fromMarkerId); + const end = detectionCapability.getMark(toMarkerId); + if (start === undefined || end === undefined) { + return; + } + + this.raw.selectLines(start.line, end.line); + if (scrollIntoView) { + this.raw.scrollToLine(start.line); + } + } + selectAll(): void { this.raw.focus(); this.raw.selectAll(); diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 57bb0fdbc7b..c8c1c38a80f 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -16,6 +16,7 @@ import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; import { ITreeContextMenuEvent, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { Action, IAction, Separator } from 'vs/base/common/actions'; import { Delayer, Limiter, RunOnceScheduler } from 'vs/base/common/async'; +import { VSBuffer } from 'vs/base/common/buffer'; import { Codicon } from 'vs/base/common/codicons'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; @@ -28,6 +29,7 @@ import { Lazy } from 'vs/base/common/lazy'; import { Disposable, DisposableStore, IDisposable, IReference, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { count } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/base/common/themables'; +import { isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./testingOutputPeek'; import { ICodeEditor, IDiffEditorConstructionOptions, isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -83,10 +85,10 @@ import { IObservableValue, MutableObservableValue, staticObservableValue } from import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue'; import { ITestExplorerFilterState } from 'vs/workbench/contrib/testing/common/testExplorerFilterState'; import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService'; -import { ITestResult, LiveTestResult, TestResultItemChange, TestResultItemChangeReason, maxCountPriority, resultItemParents } from 'vs/workbench/contrib/testing/common/testResult'; +import { ITaskRawOutput, ITestResult, ITestRunTaskResults, LiveTestResult, TestResultItemChange, TestResultItemChangeReason, maxCountPriority, resultItemParents } from 'vs/workbench/contrib/testing/common/testResult'; import { ITestResultService, ResultChangeEvent } from 'vs/workbench/contrib/testing/common/testResultService'; import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; -import { IRichLocation, ITestErrorMessage, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes'; +import { IRichLocation, ITestErrorMessage, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset, getMarkId } from 'vs/workbench/contrib/testing/common/testTypes'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; import { IShowResultOptions, ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener'; import { cmpPriority, isFailedState } from 'vs/workbench/contrib/testing/common/testingStates'; @@ -95,6 +97,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic class MessageSubject { public readonly test: ITestItem; + public readonly message: ITestMessage; public readonly messages: ITestMessage[]; public readonly expectedUri: URI; public readonly actualUri: URI; @@ -116,7 +119,7 @@ class MessageSubject { this.actualUri = buildTestUri({ ...parts, type: TestUriType.ResultActualOutput }); this.messageUri = buildTestUri({ ...parts, type: TestUriType.ResultMessage }); - const message = this.messages[this.messageIndex]; + const message = this.message = this.messages[this.messageIndex]; this.revealLocation = message.location ?? (test.item.uri && test.item.range ? { uri: test.item.uri, range: Range.lift(test.item.range) } : undefined); } } @@ -130,12 +133,24 @@ class TaskSubject { } } -type InspectSubject = MessageSubject | TaskSubject; +class TestOutputSubject { + public readonly revealLocation: undefined; + + public get outputUri() { + return buildTestUri({ resultId: this.resultId, taskIndex: this.taskIndex, testExtId: this.test.item.extId, type: TestUriType.TestOutput }); + } + + constructor(public readonly resultId: string, public readonly taskIndex: number, public readonly task: ITestRunTask, public readonly test: TestResultItem) { + } +} + +type InspectSubject = MessageSubject | TaskSubject | TestOutputSubject; const equalsSubject = (a: InspectSubject, b: InspectSubject) => a.resultId === b.resultId && a.taskIndex === b.taskIndex && ( (a instanceof MessageSubject && b instanceof MessageSubject && a.messageIndex === b.messageIndex) || - (a instanceof TaskSubject && b instanceof TaskSubject) + (a instanceof TaskSubject && b instanceof TaskSubject) || + (a instanceof TestOutputSubject && b instanceof TestOutputSubject && a.test === b.test) ); /** Iterates through every message in every result */ @@ -233,6 +248,10 @@ export class TestingPeekOpener extends Disposable implements ITestingPeekOpener return false; } + if (!('messageIndex' in parsed)) { + return false; + } + const message = result.getStateById(parsed.testExtId)?.tasks[parsed.taskIndex].messages[parsed.messageIndex]; if (!message?.location) { return false; @@ -263,12 +282,17 @@ export class TestingPeekOpener extends Disposable implements ITestingPeekOpener } const options = { pinned: false, revealIfOpened: true }; - if (current instanceof TaskSubject) { + if (current instanceof TaskSubject || current instanceof TestOutputSubject) { this.editorService.openEditor({ resource: current.outputUri, options }); return; } - const message = current.messages[current.messageIndex]; + if (current instanceof TestOutputSubject) { + this.editorService.openEditor({ resource: current.outputUri, options }); + return; + } + + const message = current.message; if (current.isDiffable) { this.editorService.openEditor({ original: { resource: current.expectedUri }, @@ -375,7 +399,7 @@ export class TestingPeekOpener extends Disposable implements ITestingPeekOpener } mapFindTestMessage(result[1], (_task, message, messageIndex, taskIndex) => { - if (!message.location || message.location.uri.toString() !== demandedUriStr) { + if (message.type !== TestMessageType.Error || !message.location || message.location.uri.toString() !== demandedUriStr) { return; } @@ -544,8 +568,7 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo } if (subject instanceof MessageSubject) { - const message = subject.messages[subject.messageIndex]; - alert(renderStringAsPlaintext(message.message)); + alert(renderStringAsPlaintext(subject.message.message)); } this.peek.value.setModel(subject); @@ -604,7 +627,13 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo testExtId: test.item.extId })); return; - } if (subject instanceof MessageSubject && subject.test.extId === test.item.extId && subject.messageIndex === messageIndex && subject.taskIndex === taskIndex && subject.resultId === result.id) { + } + + if (subject instanceof TestOutputSubject && subject.test.item.extId === test.item.extId && subject.taskIndex === taskIndex && subject.resultId === result.id) { + found = true; + } + + if (subject instanceof MessageSubject && subject.test.extId === test.item.extId && subject.messageIndex === messageIndex && subject.taskIndex === taskIndex && subject.resultId === result.id) { found = true; } } @@ -628,6 +657,13 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo continue; } + if (subject instanceof TestOutputSubject) { + if (m.test.item.extId === subject.test.item.extId && m.result.id === subject.resultId && m.taskIndex === subject.taskIndex) { + break; + } + continue; + } + if (subject.test.extId === m.test.item.extId && subject.messageIndex === m.messageIndex && subject.taskIndex === m.taskIndex && subject.resultId === m.result.id) { break; } @@ -688,6 +724,14 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo return new TaskSubject(parts.resultId, parts.taskIndex); } + if (parts.type === TestUriType.TestOutput) { + const result = this.testResults.results.find(r => r.id === parts.resultId); + const test = result?.getStateById(parts.testExtId); + const task = result?.tasks[parts.taskIndex]; + if (!test || !task) { return; } + return new TestOutputSubject(parts.resultId, parts.taskIndex, task, test); + } + const { resultId, testExtId, taskIndex, messageIndex } = parts; const test = this.testResults.getResult(parts.resultId)?.getStateById(testExtId); if (!test || !test.tasks[parts.taskIndex]) { @@ -874,7 +918,9 @@ class TestResultsPeek extends PeekViewWidget { protected override _fillBody(containerElement: HTMLElement): void { this.content.fillBody(containerElement); this.content.onDidRequestReveal(sub => { - TestingOutputPeekController.get(this.editor)?.show(sub instanceof MessageSubject ? sub.messageUri : sub.outputUri); + TestingOutputPeekController.get(this.editor)?.show(sub instanceof MessageSubject + ? sub.messageUri + : sub.outputUri); }); } @@ -882,12 +928,12 @@ class TestResultsPeek extends PeekViewWidget { * Updates the test to be shown. */ public setModel(subject: InspectSubject): Promise { - if (subject instanceof TaskSubject) { + if (subject instanceof TaskSubject || subject instanceof TestOutputSubject) { this.current = subject; return this.showInPlace(subject); } - const message = subject.messages[subject.messageIndex]; + const message = subject.message; const previous = this.current; if (!subject.revealLocation && !previous) { return Promise.resolve(); @@ -910,7 +956,7 @@ class TestResultsPeek extends PeekViewWidget { */ public async showInPlace(subject: InspectSubject) { if (subject instanceof MessageSubject) { - const message = subject.messages[subject.messageIndex]; + const message = subject.message; this.setTitle(firstLine(renderStringAsPlaintext(message.message)), stripIcons(subject.test.label)); } else { this.setTitle(localize('testOutputTitle', 'Test Output')); @@ -1057,7 +1103,7 @@ class DiffContentProvider extends Disposable implements IPeekOutputRenderer { if (!(subject instanceof MessageSubject)) { return this.clear(); } - const message = subject.messages[subject.messageIndex]; + const message = subject.message; if (!isDiffable(message)) { return this.clear(); } @@ -1162,7 +1208,7 @@ class MarkdownTestMessagePeek extends Disposable implements IPeekOutputRenderer return this.textPreview.clear(); } - const message = subject.messages[subject.messageIndex]; + const message = subject.message; if (isDiffable(message) || typeof message.message === 'string') { return this.textPreview.clear(); } @@ -1198,12 +1244,11 @@ class PlainTextMessagePeek extends Disposable implements IPeekOutputRenderer { return this.clear(); } - const message = subject.messages[subject.messageIndex]; - if (isDiffable(message) || typeof message.message !== 'string') { + const message = subject.message; + if (isDiffable(message) || message.type === TestMessageType.Output || typeof message.message !== 'string') { return this.clear(); } - const modelRef = this.model.value = await this.modelService.createModelReference(subject.messageUri); if (!this.widget.value) { this.widget.value = this.editor ? this.instantiationService.createInstance( @@ -1306,36 +1351,113 @@ class TerminalMessagePeek extends Disposable implements IPeekOutputRenderer { public async update(subject: InspectSubject) { this.outputDataListener.clear(); - if (!(subject instanceof TaskSubject)) { + if (subject instanceof TaskSubject) { + await this.updateForTaskSubject(subject); + } else if (subject instanceof TestOutputSubject || (subject instanceof MessageSubject && subject.message.type === TestMessageType.Output)) { + await this.updateForTestSubject(subject); + } else { + this.clear(); + } + } + + private async updateForTestSubject(subject: TestOutputSubject | MessageSubject) { + const that = this; + const testItem = subject instanceof TestOutputSubject ? subject.test.item : subject.test; + const terminal = await this.updateGenerically({ + subject, + getTarget: result => result?.tasks[subject.taskIndex].output, + *doInitialWrite(output, results) { + that.updateCwd(testItem.uri); + const state = subject instanceof TestOutputSubject ? subject.test : results.getStateById(testItem.extId); + if (!state) { + return; + } + + for (const message of state.tasks[subject.taskIndex].messages) { + if (message.type === TestMessageType.Output) { + yield* output.getRangeIter(message.offset, message.length); + } + } + }, + doListenForMoreData: (output, result, terminal) => result.onChange(e => { + if (e.reason === TestResultItemChangeReason.NewMessage && e.item.item.extId === testItem.extId && e.message.type === TestMessageType.Output) { + for (const chunk of output.getRangeIter(e.message.offset, e.message.length)) { + terminal.write(chunk.buffer); + } + } + }), + }); + + if (subject instanceof MessageSubject && subject.message.type === TestMessageType.Output && subject.message.marker !== undefined) { + terminal?.selectMarkedRange(getMarkId(subject.message.marker, true), getMarkId(subject.message.marker, false), /* scrollIntoView= */ true); + } + } + + private updateForTaskSubject(subject: TaskSubject) { + return this.updateGenerically({ + subject, + getTarget: result => result?.tasks[subject.taskIndex], + doInitialWrite: (task, result) => { + // Update the cwd and use the first test to try to hint at the correct cwd, + // but often this will fall back to the first workspace folder. + this.updateCwd(Iterable.find(result.tests, t => !!t.item.uri)?.item.uri); + return task.output.buffers; + }, + doListenForMoreData: (task, _result, terminal) => task.output.onDidWriteData(e => terminal.write(e.buffer)), + }); + } + + private async updateGenerically(opts: { + subject: InspectSubject; + getTarget: (result: ITestResult) => T | undefined; + doInitialWrite: (target: T, result: LiveTestResult) => Iterable; + doListenForMoreData: (target: T, result: LiveTestResult, terminal: IDetachedXtermTerminal) => IDisposable | undefined; + }) { + const result = this.resultService.getResult(opts.subject.resultId); + if (!result) { return this.clear(); } - const result = this.resultService.getResult(subject.resultId); - const task = result?.tasks[subject.taskIndex]; - if (!task) { + const target = opts.getTarget(result); + if (!target) { return this.clear(); } - // Update the cwd and use the first test to try to hint at the correct cwd, - // but often this will fall back to the first workspace folder. - this.updateCwd(Iterable.find(result.tests, t => !!t.item.uri)?.item.uri); - const terminal = await this.makeTerminal(); + let didWriteData = false; + + const pendingWrites = new MutableObservableValue(0); if (result instanceof LiveTestResult) { - let hadData = false; - for (const buffer of task.output.buffers) { - hadData ||= buffer.byteLength > 0; - terminal.write(buffer.buffer); - } - if (!hadData && !task.running) { - this.writeNotice(terminal, localize('runNoOutout', 'The test run did not record any output.')); + for (const chunk of opts.doInitialWrite(target, result)) { + didWriteData ||= chunk.byteLength > 0; + pendingWrites.value++; + terminal.write(chunk.buffer, () => pendingWrites.value--); } } else { this.writeNotice(terminal, localize('runNoOutputForPast', 'Test output is only available for new test runs.')); } this.attachTerminalToDom(terminal); - this.outputDataListener.value = task.output.onDidWriteData(e => terminal.write(e.buffer)); + this.outputDataListener.value = result instanceof LiveTestResult ? opts.doListenForMoreData(target, result, terminal) : undefined; + + if (!this.outputDataListener.value && !didWriteData) { + this.writeNotice(terminal, localize('runNoOutput', 'The test run did not record any output.')); + } + + // Ensure pending writes finish, otherwise the selection in `updateForTestSubject` + // can happen before the markers are processed. + if (pendingWrites.value > 0) { + await new Promise(resolve => { + const l = pendingWrites.onDidChange(() => { + if (pendingWrites.value === 0) { + l.dispose(); + resolve(); + } + }); + }); + } + + return terminal; } private updateCwd(testUri?: URI) { @@ -1505,8 +1627,13 @@ class TestCaseElement implements ITreeElement { return icons.testingStatesToIcons.get(this.state); } + public get outputSubject() { + return new TestOutputSubject(this.results.id, this.taskIndex, this.task, this.test); + } + constructor( private readonly results: ITestResult, + private readonly task: ITestRunTask, public readonly test: TestResultItem, public readonly taskIndex: number, ) { @@ -1549,7 +1676,6 @@ class TestMessageElement implements ITreeElement { public readonly uri: URI; public readonly location?: IRichLocation; public readonly description?: string; - public readonly marker?: number; public readonly onDidChange = Event.None; constructor( @@ -1561,7 +1687,6 @@ class TestMessageElement implements ITreeElement { const m = test.tasks[taskIndex].messages[messageIndex]; this.location = m.location; - this.marker = m.type === TestMessageType.Output ? m.marker : undefined; this.uri = this.context = buildTestUri({ type: TestUriType.ResultMessage, messageIndex, @@ -1573,7 +1698,7 @@ class TestMessageElement implements ITreeElement { this.id = this.uri.toString(); const asPlaintext = renderStringAsPlaintext(m.message); - const lines = count(asPlaintext.trimRight(), '\n'); + const lines = count(asPlaintext.trimEnd(), '\n'); this.label = firstLine(asPlaintext); if (lines > 0) { this.description = lines > 1 @@ -1649,17 +1774,20 @@ class OutputPeekTree extends Disposable { const tests = Iterable.filter(taskElem.results.tests, test => test.tasks[taskElem.index].state >= TestResultState.Running || test.tasks[taskElem.index].messages.length > 0); return Iterable.map(tests, test => ({ - element: taskElem.itemsCache.getOrCreate(test, () => new TestCaseElement(taskElem.results, test, taskElem.index)), + element: taskElem.itemsCache.getOrCreate(test, () => new TestCaseElement(taskElem.results, taskElem.task, test, taskElem.index)), incompressible: true, children: getTestChildren(taskElem.results, test, taskElem.index), })); }; const getTestChildren = (result: ITestResult, test: TestResultItem, taskIndex: number): Iterable> => { - return Iterable.map(test.tasks[taskIndex].messages, (m, messageIndex) => ({ - element: cc.getOrCreate(m, () => new TestMessageElement(result, test, taskIndex, messageIndex)), - incompressible: true, - })); + return test.tasks[taskIndex].messages + .map((m, messageIndex) => + m.type === TestMessageType.Error + ? { element: cc.getOrCreate(m, () => new TestMessageElement(result, test, taskIndex, messageIndex)), incompressible: true } + : undefined + ) + .filter(isDefined); }; const getResultChildren = (result: ITestResult): Iterable> => { @@ -1721,7 +1849,7 @@ class OutputPeekTree extends Disposable { const itemNode = taskNode.itemsCache.get(e.item); if (itemNode && this.tree.hasElement(itemNode)) { - if (e.reason === TestResultItemChangeReason.NewMessage) { + if (e.reason === TestResultItemChangeReason.NewMessage && e.message.type === TestMessageType.Error) { this.tree.setChildren(itemNode, getTestChildren(result, e.item, index), { diffIdentityProvider }); } itemNode.changeEmitter.fire(); @@ -1778,20 +1906,31 @@ class OutputPeekTree extends Disposable { this._register(onDidReveal(async ({ subject, preserveFocus = false }) => { if (subject instanceof TaskSubject) { - const resultItem = this.tree.getNode(null).children.find(c => (c.element as TestResultElement)?.id === subject.resultId); + const resultItem = this.tree.getNode(null).children.find(c => { + if (c.element instanceof TaskElement) { + return c.element.results.id === subject.resultId && c.element.index === subject.taskIndex; + } + if (c.element instanceof TestResultElement) { + return c.element.id === subject.resultId; + } + return false; + }); + if (resultItem) { - revealItem(resultItem.element as TestResultElement, preserveFocus); + revealItem(resultItem.element!, preserveFocus); } return; } - const messageNode = cc.get(subject.messages[subject.messageIndex]); - if (!messageNode || !this.tree.hasElement(messageNode)) { + const revealElement = subject instanceof TestOutputSubject + ? cc.get(subject.task)?.itemsCache.get(subject.test) + : cc.get(subject.message); + if (!revealElement || !this.tree.hasElement(revealElement)) { return; } const parents: TreeElement[] = []; - for (let parent = this.tree.getParentElement(messageNode); parent; parent = this.tree.getParentElement(parent)) { + for (let parent = this.tree.getParentElement(revealElement); parent; parent = this.tree.getParentElement(parent)) { parents.unshift(parent); } @@ -1799,11 +1938,11 @@ class OutputPeekTree extends Disposable { this.tree.expand(parent); } - if (this.tree.getRelativeTop(messageNode) === null) { - this.tree.reveal(messageNode, 0.5); + if (this.tree.getRelativeTop(revealElement) === null) { + this.tree.reveal(revealElement, 0.5); } - revealItem(messageNode, preserveFocus); + revealItem(revealElement, preserveFocus); })); this._register(this.tree.onDidOpen(async e => { @@ -1899,13 +2038,15 @@ class TestRunElementRenderer implements ICompressibleTreeRenderer this.commandService.executeCommand('vscode.revealTest', extId), )); + if (element.test.tasks[element.taskIndex].messages.some(m => m.type === TestMessageType.Output)) { + primary.push(new Action( + 'testing.outputPeek.showResultOutput', + localize('testing.showResultOutput', "Show Result Output"), + ThemeIcon.asClassName(Codicon.terminal), + undefined, + () => this.requestReveal.fire(element.outputSubject), + )); + } + secondary.push(new Action( 'testing.outputPeek.revealInExplorer', localize('testing.revealInExplorer', "Reveal in Test Explorer"), @@ -2231,8 +2382,8 @@ export class ToggleTestingPeekHistory extends Action2 { class CreationCache { private readonly v = new WeakMap(); - public get(key: object) { - return this.v.get(key); + public get(key: object): T2 | undefined { + return this.v.get(key) as T2 | undefined; } public getOrCreate(ref: object, factory: () => T2): T2 { diff --git a/src/vs/workbench/contrib/testing/common/testResult.ts b/src/vs/workbench/contrib/testing/common/testResult.ts index df34e1168a1..f289a268bfa 100644 --- a/src/vs/workbench/contrib/testing/common/testResult.ts +++ b/src/vs/workbench/contrib/testing/common/testResult.ts @@ -98,7 +98,10 @@ export interface ITaskRawOutput { readonly buffers: VSBuffer[]; readonly length: number; - getRange(start: number, end: number): VSBuffer; + /** Gets a continuous buffer for the desired range */ + getRange(start: number, length: number): VSBuffer; + /** Gets an iterator of buffers for the range; may avoid allocation of getRange() */ + getRangeIter(start: number, length: number): Iterable; } const emptyRawOutput: ITaskRawOutput = { @@ -107,6 +110,7 @@ const emptyRawOutput: ITaskRawOutput = { onDidWriteData: Event.None, endPromise: Promise.resolve(), getRange: () => VSBuffer.alloc(0), + getRangeIter: () => [], }; export class TaskRawOutput implements ITaskRawOutput { @@ -131,8 +135,18 @@ export class TaskRawOutput implements ITaskRawOutput { /** @inheritdoc */ getRange(start: number, length: number): VSBuffer { const buf = VSBuffer.alloc(length); - let bufLastWrite = 0; + for (const chunk of this.getRangeIter(start, length)) { + buf.buffer.set(chunk.buffer, bufLastWrite); + bufLastWrite += chunk.byteLength; + } + + return bufLastWrite < length ? buf.slice(0, bufLastWrite) : buf; + } + + /** @inheritdoc */ + *getRangeIter(start: number, length: number) { + let soFar = 0; let internalLastRead = 0; for (const b of this.buffers) { if (internalLastRead + b.byteLength <= start) { @@ -141,37 +155,68 @@ export class TaskRawOutput implements ITaskRawOutput { } const bstart = Math.max(0, start - internalLastRead); - const bend = Math.min(b.byteLength, bstart + length - bufLastWrite); + const bend = Math.min(b.byteLength, bstart + length - soFar); - buf.buffer.set(b.buffer.subarray(bstart, bend), bufLastWrite); - bufLastWrite += bend - bstart; + yield b.slice(bstart, bend); + soFar += bend - bstart; internalLastRead += b.byteLength; - if (bufLastWrite === buf.byteLength) { + if (soFar === length) { + break; + } + } + } + + /** + * Appends data to the output, returning the byte range where the data can be found. + */ + public append(data: VSBuffer, marker?: number) { + const offset = this.offset; + let length = data.byteLength; + if (marker === undefined) { + this.push(data); + return { offset, length }; + } + + // Bytes that should be 'trimmed' off the end of data. This is done because + // selections in the terminal are based on the entire line, and commonly + // the interesting marked range has a trailing new line. We don't want to + // select the trailing line (which might have other data) + // so we place the marker before all trailing trimbytes. + const enum TrimBytes { + CR = 13, + LF = 10, + } + + const start = VSBuffer.fromString(getMarkCode(marker, true)); + const end = VSBuffer.fromString(getMarkCode(marker, false)); + length += start.byteLength + end.byteLength; + + this.push(start); + let trimLen = data.byteLength; + for (; trimLen > 0; trimLen--) { + const last = data.buffer[trimLen - 1]; + if (last !== TrimBytes.CR && last !== TrimBytes.LF) { break; } } - return bufLastWrite < length ? buf.slice(0, bufLastWrite) : buf; + this.push(data.slice(0, trimLen)); + this.push(end); + this.push(data.slice(trimLen)); + + + return { offset, length }; } - /** - * Appends data to the output, returning the byte index where data starts. - */ - public append(data: VSBuffer, marker?: number): number { - let startOffset = this.offset; - if (marker !== undefined) { - const start = VSBuffer.fromString(getMarkCode(marker, true)); - const end = VSBuffer.fromString(getMarkCode(marker, false)); - startOffset += start.byteLength; - data = VSBuffer.concat([start, data, end]); + private push(data: VSBuffer) { + if (data.byteLength === 0) { + return; } this.buffers.push(data); this.writeDataEmitter.fire(data); this.offset += data.byteLength; - - return startOffset; } /** Signals the output has ended. */ @@ -222,7 +267,7 @@ export const enum TestResultItemChangeReason { export type TestResultItemChange = { item: TestResultItem; result: ITestResult } & ( | { reason: TestResultItemChangeReason.ComputedStateChange } | { reason: TestResultItemChangeReason.OwnStateChange; previousState: TestResultState; previousOwnDuration: number | undefined } - | { reason: TestResultItemChangeReason.NewMessage } + | { reason: TestResultItemChangeReason.NewMessage; message: ITestMessage } ); /** @@ -314,20 +359,20 @@ export class LiveTestResult implements ITestResult { const index = this.mustGetTaskIndex(taskId); const task = this.tasks[index]; - const offset = task.output.append(output, marker); + const { offset, length } = task.output.append(output, marker); const message: ITestOutputMessage = { location, message: removeAnsiEscapeCodes(preview), offset, - length: output.byteLength, - marker: marker, + length, + marker, type: TestMessageType.Output, }; const test = testId && this.testById.get(testId); if (test) { test.tasks[index].messages.push(message); - this.changeEmitter.fire({ item: test, result: this, reason: TestResultItemChangeReason.NewMessage }); + this.changeEmitter.fire({ item: test, result: this, reason: TestResultItemChangeReason.NewMessage, message }); } else { task.otherMessages.push(message); } @@ -397,7 +442,7 @@ export class LiveTestResult implements ITestResult { } entry.tasks[this.mustGetTaskIndex(taskId)].messages.push(message); - this.changeEmitter.fire({ item: entry, result: this, reason: TestResultItemChangeReason.NewMessage }); + this.changeEmitter.fire({ item: entry, result: this, reason: TestResultItemChangeReason.NewMessage, message }); } /** diff --git a/src/vs/workbench/contrib/testing/common/testingContentProvider.ts b/src/vs/workbench/contrib/testing/common/testingContentProvider.ts index bcd992edfc1..2509bbd2c12 100644 --- a/src/vs/workbench/contrib/testing/common/testingContentProvider.ts +++ b/src/vs/workbench/contrib/testing/common/testingContentProvider.ts @@ -94,6 +94,16 @@ export class TestingContentProvider implements IWorkbenchContribution, ITextMode if (message?.type === TestMessageType.Error) { text = message.actual; } break; } + case TestUriType.TestOutput: { + text = ''; + const output = result.tasks[parsed.taskIndex].output; + for (const message of test.tasks[parsed.taskIndex].messages) { + if (message.type === TestMessageType.Output) { + text += removeAnsiEscapeCodes(output.getRange(message.offset, message.length).toString()); + } + } + break; + } case TestUriType.ResultExpectedOutput: { const message = test.tasks[parsed.taskIndex].messages[parsed.messageIndex]; if (message?.type === TestMessageType.Error) { text = message.expected; } diff --git a/src/vs/workbench/contrib/testing/common/testingUri.ts b/src/vs/workbench/contrib/testing/common/testingUri.ts index 1e3b99eb21f..e5cf6102843 100644 --- a/src/vs/workbench/contrib/testing/common/testingUri.ts +++ b/src/vs/workbench/contrib/testing/common/testingUri.ts @@ -9,9 +9,15 @@ import { URI } from 'vs/base/common/uri'; export const TEST_DATA_SCHEME = 'vscode-test-data'; export const enum TestUriType { + /** All console output for a task */ TaskOutput, + /** All console output for a test in a task */ + TestOutput, + /** Specific message in a test */ ResultMessage, + /** Specific actual output message in a test */ ResultActualOutput, + /** Specific expected output message in a test */ ResultExpectedOutput, } @@ -27,12 +33,16 @@ interface IResultTestUri { testExtId: string; } +interface ITestOutputReference extends IResultTestUri { + type: TestUriType.TestOutput; +} + interface IResultTestMessageReference extends IResultTestUri { type: TestUriType.ResultMessage; messageIndex: number; } -interface IResultTestOutputReference extends IResultTestUri { +interface ITestDiffOutputReference extends IResultTestUri { type: TestUriType.ResultActualOutput | TestUriType.ResultExpectedOutput; messageIndex: number; } @@ -40,7 +50,8 @@ interface IResultTestOutputReference extends IResultTestUri { export type ParsedTestUri = | IAllOutputReference | IResultTestMessageReference - | IResultTestOutputReference; + | ITestDiffOutputReference + | ITestOutputReference; const enum TestUriParts { Results = 'results', @@ -54,50 +65,55 @@ const enum TestUriParts { export const parseTestUri = (uri: URI): ParsedTestUri | undefined => { const type = uri.authority; - const [locationId, ...request] = uri.path.slice(1).split('/'); + const [resultId, ...request] = uri.path.slice(1).split('/'); if (request[0] === TestUriParts.Messages) { const taskIndex = Number(request[1]); + const testExtId = uri.query; const index = Number(request[2]); const part = request[3]; - const testExtId = uri.query; if (type === TestUriParts.Results) { switch (part) { case TestUriParts.Text: - return { resultId: locationId, taskIndex, testExtId, messageIndex: index, type: TestUriType.ResultMessage }; + return { resultId, taskIndex, testExtId, messageIndex: index, type: TestUriType.ResultMessage }; case TestUriParts.ActualOutput: - return { resultId: locationId, taskIndex, testExtId, messageIndex: index, type: TestUriType.ResultActualOutput }; + return { resultId, taskIndex, testExtId, messageIndex: index, type: TestUriType.ResultActualOutput }; case TestUriParts.ExpectedOutput: - return { resultId: locationId, taskIndex, testExtId, messageIndex: index, type: TestUriType.ResultExpectedOutput }; + return { resultId, taskIndex, testExtId, messageIndex: index, type: TestUriType.ResultExpectedOutput }; + case TestUriParts.Messages: } } } if (request[0] === TestUriParts.AllOutput) { - return { resultId: locationId, taskIndex: Number(request[1]), type: TestUriType.TaskOutput }; + const testExtId = uri.query; + const taskIndex = Number(request[1]); + return testExtId + ? { resultId, taskIndex, testExtId, type: TestUriType.TestOutput } + : { resultId, taskIndex, type: TestUriType.TaskOutput }; } return undefined; }; export const buildTestUri = (parsed: ParsedTestUri): URI => { - if (parsed.type === TestUriType.TaskOutput) { - return URI.from({ - scheme: TEST_DATA_SCHEME, - authority: TestUriParts.Results, - path: ['', parsed.resultId, TestUriParts.AllOutput, parsed.taskIndex].join('/'), - }); - } - const uriParts = { scheme: TEST_DATA_SCHEME, authority: TestUriParts.Results }; - const msgRef = (locationId: string, ...remaining: (string | number)[]) => + + if (parsed.type === TestUriType.TaskOutput) { + return URI.from({ + ...uriParts, + path: ['', parsed.resultId, TestUriParts.AllOutput, parsed.taskIndex].join('/'), + }); + } + + const msgRef = (resultId: string, ...remaining: (string | number)[]) => URI.from({ ...uriParts, query: parsed.testExtId, - path: ['', locationId, TestUriParts.Messages, ...remaining].join('/'), + path: ['', resultId, TestUriParts.Messages, ...remaining].join('/'), }); switch (parsed.type) { @@ -107,6 +123,12 @@ export const buildTestUri = (parsed: ParsedTestUri): URI => { return msgRef(parsed.resultId, parsed.taskIndex, parsed.messageIndex, TestUriParts.ExpectedOutput); case TestUriType.ResultMessage: return msgRef(parsed.resultId, parsed.taskIndex, parsed.messageIndex, TestUriParts.Text); + case TestUriType.TestOutput: + return URI.from({ + ...uriParts, + query: parsed.testExtId, + path: ['', parsed.resultId, TestUriParts.AllOutput, parsed.taskIndex].join('/'), + }); default: assertNever(parsed, 'Invalid test uri'); } diff --git a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts index 6e3dbf4ee54..4f19df8e1a4 100644 --- a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts @@ -326,9 +326,11 @@ suite('Workbench - Test Results Service', () => { const a1 = ctrl.append(VSBuffer.fromString('12345'), 1); const a2 = ctrl.append(VSBuffer.fromString('67890'), 1234); + const a3 = ctrl.append(VSBuffer.fromString('with new line\r\n'), 4); - assert.deepStrictEqual(ctrl.getRange(a1, 5), VSBuffer.fromString('12345')); - assert.deepStrictEqual(ctrl.getRange(a2, 5), VSBuffer.fromString('67890')); + assert.deepStrictEqual(ctrl.getRange(a1.offset, a1.length), VSBuffer.fromString('\x1b]633;SetMark;Id=s1;Hidden\x0712345\x1b]633;SetMark;Id=e1;Hidden\x07')); + assert.deepStrictEqual(ctrl.getRange(a2.offset, a2.length), VSBuffer.fromString('\x1b]633;SetMark;Id=s1234;Hidden\x0767890\x1b]633;SetMark;Id=e1234;Hidden\x07')); + assert.deepStrictEqual(ctrl.getRange(a3.offset, a3.length), VSBuffer.fromString('\x1b]633;SetMark;Id=s4;Hidden\x07with new line\x1b]633;SetMark;Id=e4;Hidden\x07\r\n')); }); }); }); From 1c9d5ca9ff707ede95f29b7c6e8c87c5cd4ed152 Mon Sep 17 00:00:00 2001 From: weartist Date: Wed, 9 Aug 2023 09:53:06 +0800 Subject: [PATCH 34/69] From focus Search to focus ToC --- src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index f05db46a48a..d0cf76cd3cf 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -342,7 +342,7 @@ export class SettingsEditor2 extends EditorPane { focusNotifiers: [this], focusNextWidget: () => { if (this.searchWidget.inputWidget.hasWidgetFocus()) { - this.focusSettings(); + this.focusTOC(); } }, focusPreviousWidget: () => { From b435cca931aba40343f20149b57ac2b092621baf Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 9 Aug 2023 09:30:02 +0200 Subject: [PATCH 35/69] check for new type --- src/vs/workbench/contrib/chat/common/chatServiceImpl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index ea8800afad8..9921ab1ff74 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -487,9 +487,9 @@ export class ChatService extends Disposable implements IChatService { if (typeof request.message !== 'string' || !request.response) { continue; } - if (isMarkdownString(request.response.response)) { + if (isMarkdownString(request.response.response.value)) { history.push({ role: ChatMessageRole.User, content: request.message }); - history.push({ role: ChatMessageRole.Assistant, content: request.response?.response.value }); + history.push({ role: ChatMessageRole.Assistant, content: request.response.response.value.value }); } } await this.chatSlashCommandService.executeCommand(resolvedCommand, message.substring(resolvedCommand.length + 1).trimStart(), new Progress(p => progressCallback(p)), history, token); From efb8eeb768659ac3eed66e56609ec622864f4291 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 9 Aug 2023 09:30:12 +0200 Subject: [PATCH 36/69] add `SlashCommandMetadata` --- src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts | 4 ++-- src/vs/workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostChatSlashCommand.ts | 4 ++-- src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts | 6 +++++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts b/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts index dff1def81c7..5ec7c93edb2 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts @@ -28,14 +28,14 @@ export class MainThreadChatSlashCommands implements MainThreadChatSlashCommandsS this._commands.clearAndDisposeAll(); } - $registerCommand(handle: number, name: string): void { + $registerCommand(handle: number, name: string, detail: string): void { if (!this._chatSlashCommandService.hasCommand(name)) { // dynamic slash commands! this._chatSlashCommandService.registerSlashData({ name, id: name, - detail: '' + detail }); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 774af535319..76565ab6f14 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1140,7 +1140,7 @@ export interface ExtHostChatProviderShape { } export interface MainThreadChatSlashCommandsShape extends IDisposable { - $registerCommand(handle: number, name: string): void; + $registerCommand(handle: number, name: string, detail: string): void; $unregisterCommand(handle: number): void; $handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise; } diff --git a/src/vs/workbench/api/common/extHostChatSlashCommand.ts b/src/vs/workbench/api/common/extHostChatSlashCommand.ts index 6fbfeab805a..2282d15c900 100644 --- a/src/vs/workbench/api/common/extHostChatSlashCommand.ts +++ b/src/vs/workbench/api/common/extHostChatSlashCommand.ts @@ -31,11 +31,11 @@ export class ExtHostChatSlashCommands implements ExtHostChatSlashCommandsShape { this._proxy = mainContext.getProxy(MainContext.MainThreadChatSlashCommands); } - registerCommand(extension: ExtensionIdentifier, name: string, command: vscode.SlashCommand): IDisposable { + registerCommand(extension: ExtensionIdentifier, name: string, command: vscode.SlashCommand, metadata: vscode.SlashCommandMetadata): IDisposable { const handle = ExtHostChatSlashCommands._idPool++; this._commands.set(handle, { extension, command }); - this._proxy.$registerCommand(handle, name); + this._proxy.$registerCommand(handle, name, metadata.description); return toDisposable(() => { this._proxy.$unregisterCommand(handle); diff --git a/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts b/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts index 3be6ac89cc0..5519c112ebe 100644 --- a/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts +++ b/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts @@ -32,12 +32,16 @@ declare module 'vscode' { // followUp?: InteractiveSessionFollowup[]; } + export interface SlashCommandMetadata { + description: string; + } + export interface SlashCommand { (prompt: ChatMessage, context: SlashCommandContext, progress: Progress, token: CancellationToken): Thenable; } export namespace chat { - export function registerSlashCommand(name: string, command: SlashCommand): Disposable; + export function registerSlashCommand(name: string, command: SlashCommand, metadata: SlashCommandMetadata): Disposable; } } From 465b94787f0d4c5c0993caa0388eedc7647ed873 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 9 Aug 2023 10:59:39 +0200 Subject: [PATCH 37/69] graceful metadata handling --- src/vs/workbench/api/common/extHost.api.impl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index deeece7b083..383e886c8c1 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1338,9 +1338,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'chatProvider'); return extHostChatProvider.registerProvider(extension.identifier, id, provider, metadata); }, - registerSlashCommand(name: string, command: vscode.SlashCommand) { + registerSlashCommand(name: string, command: vscode.SlashCommand, metadata?: vscode.SlashCommandMetadata) { checkProposedApiEnabled(extension, 'chatSlashCommands'); - return extHostChatSlashCommands.registerCommand(extension.identifier, name, command); + return extHostChatSlashCommands.registerCommand(extension.identifier, name, command, metadata ?? { description: '' }); }, requestChatAccess(id: string) { checkProposedApiEnabled(extension, 'chatRequestAccess'); From 6dd6bdfb002360295ddab9434f94cecec1f7d081 Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 9 Aug 2023 18:30:08 +0900 Subject: [PATCH 38/69] chore: update Electron v25.4.0 (#189904) * chore: some cleanups from the Electron v25 update * chore: update Electron@25.4.0 * chore: update issue reference * chore: bump distro --- .yarnrc | 2 +- .../azure-pipelines/linux/product-build-linux.yml | 2 ++ build/npm/preinstall.js | 14 ++++++++------ package.json | 2 +- src/vs/base/parts/ipc/electron-main/ipcMain.ts | 8 ++++++-- .../platform/windows/electron-main/windowImpl.ts | 6 +++++- 6 files changed, 23 insertions(+), 11 deletions(-) diff --git a/.yarnrc b/.yarnrc index c60b0d7893b..6826ca67118 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,5 +1,5 @@ disturl "https://electronjs.org/headers" target "25.4.0" -ms_build_id "22901934" +ms_build_id "22958381" runtime "electron" build_from_source "true" diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 4fa4457e6ed..2145e588b1c 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -126,6 +126,7 @@ steps: docker run -e GITHUB_TOKEN -e npm_config_arch -e NPM_REGISTRY \ -e VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME -e VSCODE_HOST_MOUNT \ -e ELECTRON_SKIP_BINARY_DOWNLOAD -e PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD \ + -e VSCODE_SKIP_NODE_VERSION_CHECK \ -v /mnt/vss/_work/1/s:/home/builduser/vscode -v /mnt/vss/_work/1/s/.build/.netrc:/home/builduser/.netrc \ -v /var/run/docker.sock:/var/run/docker.sock \ -u 1000:1000 \ @@ -138,6 +139,7 @@ steps: NPM_REGISTRY: "$(NPM_REGISTRY)" ELECTRON_SKIP_BINARY_DOWNLOAD: 1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + VSCODE_SKIP_NODE_VERSION_CHECK: 1 GITHUB_TOKEN: "$(github-distro-mixin-password)" VSCODE_HOST_MOUNT: "/mnt/vss/_work/1/s" ${{ if or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64')) }}: diff --git a/build/npm/preinstall.js b/build/npm/preinstall.js index 0e4a001fd4c..b6a0a746780 100644 --- a/build/npm/preinstall.js +++ b/build/npm/preinstall.js @@ -9,12 +9,14 @@ const majorNodeVersion = parseInt(nodeVersion[1]); const minorNodeVersion = parseInt(nodeVersion[2]); const patchNodeVersion = parseInt(nodeVersion[3]); -if (majorNodeVersion < 18 || (majorNodeVersion === 18 && minorNodeVersion < 15)) { - console.error('\033[1;31m*** Please use node.js versions >=18.15.x and <19.\033[0;0m'); - // err = true; enable once update unit test docker images are updated #189885 -} -if (majorNodeVersion >= 19) { - console.warn('\033[1;31m*** Warning: Versions of node.js >= 19 have not been tested.\033[0;0m') +if (!process.env['VSCODE_SKIP_NODE_VERSION_CHECK']) { + if (majorNodeVersion < 18 || (majorNodeVersion === 18 && minorNodeVersion < 15)) { + console.error('\033[1;31m*** Please use node.js versions >=18.15.x and <19.\033[0;0m'); + err = true; + } + if (majorNodeVersion >= 19) { + console.warn('\033[1;31m*** Warning: Versions of node.js >= 19 have not been tested.\033[0;0m') + } } const path = require('path'); diff --git a/package.json b/package.json index 5cbce3c0e96..b6f40a6fd9d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.82.0", - "distro": "841e44443565bdc380b14a231d7419985956f7ab", + "distro": "a94daa7ddfb25b11bd8f3376c01e30ff356d0a2b", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/base/parts/ipc/electron-main/ipcMain.ts b/src/vs/base/parts/ipc/electron-main/ipcMain.ts index 95e8e4e1c32..6fe43ab0b3a 100644 --- a/src/vs/base/parts/ipc/electron-main/ipcMain.ts +++ b/src/vs/base/parts/ipc/electron-main/ipcMain.ts @@ -111,8 +111,12 @@ class ValidatedIpcMain implements Event.NodeEventEmitter { const sender = event.senderFrame; const url = sender.url; - if (!url) { - return true; // TODO@electron this only seems to happen from playwright runs (https://github.com/microsoft/vscode/issues/147301) + // `url` can be `undefined` when running tests from playwright https://github.com/microsoft/vscode/issues/147301 + // and `url` can be `about:blank` when reloading the window + // from performance tab of devtools https://github.com/electron/electron/issues/39427. + // It is fine to skip the checks in these cases. + if (!url || url === 'about:blank') { + return true; } let host = 'unknown'; diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 089c19bbeec..ab87e5ac778 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -1106,7 +1106,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { // macOS: traffic lights else if (isMacintosh && options.height !== undefined) { const verticalOffset = (options.height - 15) / 2; // 15px is the height of the traffic lights - this._win.setTrafficLightPosition({ x: verticalOffset, y: verticalOffset }); + if (!verticalOffset) { + this._win.setWindowButtonPosition(null); + } else { + this._win.setWindowButtonPosition({ x: verticalOffset, y: verticalOffset }); + } } } From e7e6dd3c6e757b0d5ca51f0dddf3d43cbe8b566c Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 9 Aug 2023 11:51:38 +0200 Subject: [PATCH 39/69] fix tests --- .../workbench/contrib/chat/test/common/chatService.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index 2b8ea83543d..a2f01a9d9ed 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -20,6 +20,9 @@ import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; class SimpleTestProvider extends Disposable implements IChatProvider { private static sessionId = 0; @@ -61,7 +64,9 @@ suite('Chat', () => { let instantiationService: TestInstantiationService; suiteSetup(async () => { - instantiationService = new TestInstantiationService(); + instantiationService = new TestInstantiationService(new ServiceCollection( + [IChatSlashCommandService, new SyncDescriptor(ChatSlashCommandService)] + )); instantiationService.stub(IStorageService, storageService = new TestStorageService()); instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(IExtensionService, new TestExtensionService()); From dd47c4f28bc23113f6b3b90bda82886bb054e5a0 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 9 Aug 2023 12:30:02 +0200 Subject: [PATCH 40/69] also for mac set `no-drag` for CC fixes https://github.com/microsoft/vscode/issues/189973 --- .../workbench/browser/parts/titlebar/media/titlebarpart.css | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 83c553b3c77..315429f1f85 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -127,11 +127,6 @@ /* Window Title Menu */ .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center { z-index: 2500; -} - -/* MacOS Desktop supports click event despite `drag` and therefore we don't need to clear it */ -.monaco-workbench:not(.mac) .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center, -.monaco-workbench.mac.web .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center { -webkit-app-region: no-drag; } From e766158f04cc5aeba903766ff644102326677aea Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 9 Aug 2023 15:53:23 +0200 Subject: [PATCH 41/69] joh/everyday landfowl (#190040) * emit event when slash commands change * further align `IChatSlashData` with `ISashCommand` * migrate `/clear` onto new contributable slash commands fyi @roblourens * validate statically contributed slash commands --- .../browser/mainThreadChatSlashCommands.ts | 3 +- .../chat/browser/actions/chatClearActions.ts | 4 +- .../contrib/chat/browser/chat.contribution.ts | 22 ++++++- .../contrib/chat/browser/chatWidget.ts | 22 +------ .../contrib/chat/common/chatServiceImpl.ts | 4 +- .../contrib/chat/common/chatSlashCommands.ts | 64 +++++++++++++++---- 6 files changed, 81 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts b/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts index 5ec7c93edb2..a9ca433b4ba 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts @@ -33,8 +33,7 @@ export class MainThreadChatSlashCommands implements MainThreadChatSlashCommandsS if (!this._chatSlashCommandService.hasCommand(name)) { // dynamic slash commands! this._chatSlashCommandService.registerSlashData({ - name, - id: name, + command: name, detail }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index ce18dd8dc11..1d4df4784fb 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -19,6 +19,8 @@ import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInp import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +export const ACTION_ID_CLEAR_CHAT = `workbench.action.chat.clear`; + export function registerClearActions() { registerAction2(class ClearEditorAction extends Action2 { @@ -48,7 +50,7 @@ export function registerClearActions() { registerAction2(class GlobalClearChatAction extends Action2 { constructor() { super({ - id: `workbench.action.chat.clear`, + id: ACTION_ID_CLEAR_CHAT, title: { value: localize('interactiveSession.clear.label', "Clear"), original: 'Clear' diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index bcd455bd275..6d78034a68c 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -36,7 +36,7 @@ import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/s import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import '../common/chatColors'; import { registerMoveActions } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions'; -import { registerClearActions } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; +import { ACTION_ID_CLEAR_CHAT, registerClearActions } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; import { AccessibleViewAction, AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; @@ -48,6 +48,7 @@ import { ChatWelcomeMessageModel } from 'vs/workbench/contrib/chat/common/chatMo import { IMarkdownString } from 'vs/base/common/htmlContent'; import { ChatProviderService, IChatProviderService } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; +import { ICommandService } from 'vs/platform/commands/common/commands'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -216,9 +217,28 @@ class ChatAccessibleViewContribution extends Disposable { } } +class ChatSlashStaticSlashCommandsContribution extends Disposable { + + constructor( + @IChatSlashCommandService slashCommandService: IChatSlashCommandService, + @ICommandService commandService: ICommandService, + ) { + super(); + this._store.add(slashCommandService.registerSlashCommand({ + command: 'clear', + detail: nls.localize('clear', "Clear the session"), + sortText: 'z_clear', + executeImmediately: true + }, async () => { + commandService.executeCommand(ACTION_ID_CLEAR_CHAT); + })); + } +} + const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(ChatResolverContribution, LifecyclePhase.Starting); workbenchContributionsRegistry.registerWorkbenchContribution(ChatAccessibleViewContribution, LifecyclePhase.Eventually); +workbenchContributionsRegistry.registerWorkbenchContribution(ChatSlashStaticSlashCommandsContribution, LifecyclePhase.Eventually); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(ChatEditorInput.TypeID, ChatEditorInputSerializer); registerChatActions(); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index c0a162bfdf1..78f8788316e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -13,7 +13,6 @@ import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/chat'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -21,7 +20,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { IViewsService } from 'vs/workbench/common/views'; -import { clearChatSession } from 'vs/workbench/contrib/chat/browser/actions/chatClear'; import { ChatTreeItem, IChatAccessibilityService, IChatCodeBlockInfo, IChatWidget, IChatWidgetService, IChatWidgetViewContext } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { ChatAccessibilityProvider, ChatListDelegate, ChatListItemRenderer, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; @@ -264,17 +262,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (!this.slashCommandsPromise) { this.slashCommandsPromise = this.chatService.getSlashCommands(this.viewModel.sessionId, CancellationToken.None).then(commands => { - // If this becomes a repeated pattern, we should have a real internal slash command provider system - const clearCommand: ISlashCommand = { - command: 'clear', - sortText: 'z_clear', - detail: localize('clear', "Clear the session"), - executeImmediately: true - }; - this.lastSlashCommands = [ - ...(commands ?? []), - clearCommand - ]; + this.lastSlashCommands = commands ?? []; return this.lastSlashCommands; }); } @@ -431,13 +419,6 @@ export class ChatWidget extends Disposable implements IChatWidget { async acceptInput(query?: string | IChatReplyFollowup): Promise { if (this.viewModel) { const editorValue = this.inputPart.inputEditor.getValue(); - - // Shortcut for /clear command - if (!query && editorValue.trim() === '/clear' || typeof query === 'string' && query.trim() === '/clear') { - // Small hack, if this becomes a repeated pattern, we should have a real internal slash command provider system - this.instantiationService.invokeFunction(clearChatSession, this); - return; - } this._chatAccessibilityService.acceptRequest(); const input = query ?? editorValue; const usedSlashCommand = this.lookupSlashCommand(typeof input === 'string' ? input : input.message); @@ -605,4 +586,3 @@ export class ChatWidgetService implements IChatWidgetService { ); } } - diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 9921ab1ff74..6332f29721c 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -597,8 +597,10 @@ export class ChatService extends Disposable implements IChatService { const serviceResults = this.chatSlashCommandService.getCommands().map(data => { return { - command: data.name, + command: data.command, detail: data.detail, + sortText: data.sortText, + executeImmediately: data.executeImmediately }; }); diff --git a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts index 576f5a502c8..b64d5e6a5b8 100644 --- a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts +++ b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; +import { Event, Emitter } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProgress } from 'vs/platform/progress/common/progress'; @@ -18,11 +19,11 @@ import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/exte const slashItem: IJSONSchema = { type: 'object', - required: ['name', 'detail'], + required: ['command', 'detail'], properties: { - name: { + command: { type: 'string', - markdownDescription: localize('name', "The name of the slash command which will be used as prefix.") + markdownDescription: localize('command', "The name of the slash command which will be used as prefix.") }, detail: { type: 'string', @@ -42,7 +43,7 @@ const slashItems: IJSONSchema = { ] }; -export const slashesExtPoint = ExtensionsRegistry.registerExtensionPoint({ +export const slashesExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'slashes', jsonSchema: slashItems }); @@ -50,9 +51,23 @@ export const slashesExtPoint = ExtensionsRegistry.registerExtensionPoint; registerSlashData(data: IChatSlashData): IDisposable; registerSlashCallback(id: string, command: IChatSlashCallback): IDisposable; + registerSlashCommand(data: IChatSlashData, command: IChatSlashCallback): IDisposable; executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise; getCommands(): Array; hasCommand(id: string): boolean; @@ -80,6 +97,9 @@ export class ChatSlashCommandService implements IChatSlashCommandService { private readonly _commands = new Map(); + private readonly _onDidChangeCommands = new Emitter(); + readonly onDidChangeCommands: Event = this._onDidChangeCommands.event; + constructor(@IExtensionService private readonly _extensionService: IExtensionService) { const contributions = new DisposableStore(); @@ -96,7 +116,13 @@ export class ChatSlashCommandService implements IChatSlashCommandService { const { value } = entry; for (const candidate of Iterable.wrap(value)) { - contributions.add(this.registerSlashData({ ...candidate, id: candidate.name })); + + if (!isChatSlashData(candidate)) { + entry.collector.error(localize('invalid', "Invalid {0}: {1}", slashesExtPoint.name, JSON.stringify(candidate))); + continue; + } + + contributions.add(this.registerSlashData({ ...candidate })); } } }); @@ -104,14 +130,21 @@ export class ChatSlashCommandService implements IChatSlashCommandService { dispose(): void { this._commands.clear(); + this._onDidChangeCommands.dispose(); } registerSlashData(data: IChatSlashData): IDisposable { - if (this._commands.has(data.id)) { - throw new Error(`Already registered a command with id ${data.id}}`); + if (this._commands.has(data.command)) { + throw new Error(`Already registered a command with id ${data.command}}`); } - this._commands.set(data.id, { data }); - return toDisposable(() => this._commands.delete(data.id)); + this._commands.set(data.command, { data }); + this._onDidChangeCommands.fire(); + + return toDisposable(() => { + if (this._commands.delete(data.command)) { + this._onDidChangeCommands.fire(); + } + }); } registerSlashCallback(id: string, command: IChatSlashCallback): IDisposable { @@ -123,6 +156,13 @@ export class ChatSlashCommandService implements IChatSlashCommandService { return toDisposable(() => data.command = undefined); } + registerSlashCommand(data: IChatSlashData, command: IChatSlashCallback): IDisposable { + return combinedDisposable( + this.registerSlashData(data), + this.registerSlashCallback(data.command, command) + ); + } + getCommands(): Array { return Array.from(this._commands.values(), v => v.data); } From 159397cf708bda5fe8269e441694d34fafe85863 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Aug 2023 07:54:29 -0700 Subject: [PATCH 42/69] fix issue with go to symbol for lists --- .../accessibility/browser/accessibleView.ts | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 338183d84df..b7d1e55d2d6 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -206,6 +206,7 @@ class AccessibleView extends Disposable { return; } const symbols: IAccessibleViewSymbol[] = []; + let firstListItem: string | undefined; for (const token of tokens) { let label: string | undefined = undefined; if ('type' in token) { @@ -215,22 +216,29 @@ class AccessibleView extends Disposable { case 'code': label = token.text; break; - case 'list': + case 'list': { + const firstItem = token.items?.[0]; + if (!firstItem) { + break; + } + firstListItem = `- ${firstItem.text}`; label = token.items?.map(i => i.text).join(', '); break; + } } } else { label = token.label; } if (label) { - symbols.push({ info: label, label: localize('symbolLabel', "({0}) {1}", token.type, label), ariaLabel: localize('symbolLabelAria', "({0}) {1}", token.type, label) }); + symbols.push({ info: label, label: localize('symbolLabel', "({0}) {1}", token.type, label), ariaLabel: localize('symbolLabelAria', "({0}) {1}", token.type, label), firstListItem }); + firstListItem = undefined; } } return symbols; } showSymbol(provider: IAccessibleContentProvider, symbol: IAccessibleViewSymbol): void { - const index = provider.provideContent().split('\n').findIndex(line => line.includes(symbol.info.split('\n')[0])) ?? -1; + const index = provider.provideContent().split('\n').findIndex(line => line.includes(symbol.info.split('\n')[0]) || (symbol.firstListItem && line.includes(symbol.firstListItem))) ?? -1; if (index >= 0) { this.show(provider); this._editorWidget.revealLine(index + 1); @@ -365,17 +373,19 @@ class AccessibleView extends Disposable { accessibleViewHelpProvider.provideContent = () => this._getAccessibleViewHelpDialogContent(accessibleViewHelpProvider); accessibleViewHelpProvider.onClose = () => this.show(previousProvider); this._contextViewService.hideContextView(); + this._accessiblityHelpIsShown.set(true); + const delegate: IContextViewDelegate = { + getAnchor: () => { return { x: (window.innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; }, + render: (container) => { + container.classList.add('accessible-view-container'); + return this._render(accessibleViewHelpProvider, container, true); + }, + onHide: () => { + this._accessiblityHelpIsShown.reset(); + } + }; // HACK: Delay to allow the context view to hide #186514 - setTimeout(() => { - const delegate: IContextViewDelegate = { - getAnchor: () => { return { x: (window.innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; }, - render: (container) => { - container.classList.add('accessible-view-container'); - return this._render(accessibleViewHelpProvider, container, true); - } - }; - this._contextViewService.showContextView(delegate); - }, 100); + setTimeout(() => this._contextViewService.showContextView(delegate), 100); } private _getAccessibleViewHelpDialogContent(provider: IAccessibleContentProvider): string { @@ -535,13 +545,14 @@ class AccessibleViewSymbolQuickPick { interface IAccessibleViewSymbol extends IPickerQuickAccessItem { info: string; + firstListItem?: string; } class AccessibleViewNextAction extends Action2 { constructor() { super({ id: 'editor.action.accessibleViewNext', - precondition: ContextKeyExpr.and(accessibleViewIsShown, accessibilityHelpIsShown.negate()), + precondition: accessibleViewIsShown, keybinding: { primary: KeyMod.Alt | KeyCode.BracketRight, weight: KeybindingWeight.WorkbenchContrib @@ -567,7 +578,7 @@ class AccessibleViewPreviousAction extends Action2 { constructor() { super({ id: 'editor.action.accessibleViewPrevious', - precondition: ContextKeyExpr.and(accessibleViewIsShown, accessibilityHelpIsShown.negate()), + precondition: accessibleViewIsShown, keybinding: { primary: KeyMod.Alt | KeyCode.BracketLeft, weight: KeybindingWeight.WorkbenchContrib @@ -595,7 +606,7 @@ class AccessibleViewGoToSymbolAction extends Action2 { constructor() { super({ id: 'editor.action.accessibleViewGoToSymbol', - precondition: ContextKeyExpr.and(accessibleViewIsShown, accessibilityHelpIsShown.negate()), + precondition: accessibleViewIsShown, keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO, weight: KeybindingWeight.WorkbenchContrib + 10 From a0fbf8c0c5e47d6f1990780a1a41aec1d3e50c4f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Aug 2023 08:17:27 -0700 Subject: [PATCH 43/69] fix other issues --- .../accessibility/browser/accessibleView.ts | 85 ++++++++----------- 1 file changed, 36 insertions(+), 49 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index b7d1e55d2d6..f9d1349395e 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -150,11 +150,6 @@ class AccessibleView extends Disposable { if (!provider) { return; } - if (provider.options.type === AccessibleViewType.Help) { - this._accessiblityHelpIsShown.set(true); - } else { - this._accessibleViewIsShown.set(true); - } const delegate: IContextViewDelegate = { getAnchor: () => { return { x: (window.innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; }, render: (container) => { @@ -162,11 +157,6 @@ class AccessibleView extends Disposable { return this._render(provider!, container); }, onHide: () => { - if (provider!.options.type === AccessibleViewType.Help) { - this._accessiblityHelpIsShown.reset(); - } else { - this._accessibleViewIsShown.reset(); - } this._currentProvider = undefined; } }; @@ -254,10 +244,21 @@ class AccessibleView extends Disposable { alert(localize('disableAccessibilityHelp', '{0} accessibility verbosity is now disabled', this._currentProvider.verbositySettingKey)); } + private _updateContextKeys(provider: IAccessibleContentProvider, shown: boolean): void { + if (provider.options.type === AccessibleViewType.Help) { + this._accessiblityHelpIsShown.set(shown); + this._accessibleViewIsShown.set(!shown); + } else { + this._accessibleViewIsShown.set(shown); + this._accessiblityHelpIsShown.set(!shown); + } + } + private _render(provider: IAccessibleContentProvider, container: HTMLElement, internal?: boolean): IDisposable { if (!internal) { this._currentProvider = provider; } + this._updateContextKeys(provider, true); const value = this._configurationService.getValue(provider.verbositySettingKey); const readMoreLink = provider.options.readMoreUrl ? localize("openDoc", "\nPress H now to open a browser window with more information related to accessibility.\n") : ''; let disableHelpHint = ''; @@ -322,6 +323,7 @@ class AccessibleView extends Disposable { if (e.keyCode === KeyCode.Escape) { e.stopPropagation(); this._contextViewService.hideContextView(); + this._updateContextKeys(provider, false); // HACK: Delay to allow the context view to hide #186514 setTimeout(() => provider.onClose(), 100); } else if (e.keyCode === KeyCode.KeyH && provider.options.readMoreUrl) { @@ -371,9 +373,11 @@ class AccessibleView extends Disposable { const accessibleViewHelpProvider = Object.assign({}, this._currentProvider); accessibleViewHelpProvider.options.type = AccessibleViewType.Help; accessibleViewHelpProvider.provideContent = () => this._getAccessibleViewHelpDialogContent(accessibleViewHelpProvider); - accessibleViewHelpProvider.onClose = () => this.show(previousProvider); + accessibleViewHelpProvider.onClose = () => { + previousProvider.options.type = AccessibleViewType.View; + this.show(previousProvider); + }; this._contextViewService.hideContextView(); - this._accessiblityHelpIsShown.set(true); const delegate: IContextViewDelegate = { getAnchor: () => { return { x: (window.innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; }, render: (container) => { @@ -547,7 +551,16 @@ interface IAccessibleViewSymbol extends IPickerQuickAccessItem { info: string; firstListItem?: string; } - +const accessibleViewMenu = { + id: MenuId.AccessibleView, + group: 'navigation', + when: accessibleViewIsShown +}; +const commandPalette = { + id: MenuId.CommandPalette, + group: '', + order: 1 +}; class AccessibleViewNextAction extends Action2 { constructor() { super({ @@ -557,12 +570,7 @@ class AccessibleViewNextAction extends Action2 { primary: KeyMod.Alt | KeyCode.BracketRight, weight: KeybindingWeight.WorkbenchContrib }, - menu: [{ - id: MenuId.CommandPalette, - group: '', - order: 1 - }, - { id: MenuId.AccessibleView, group: 'navigation' }], + menu: [commandPalette, accessibleViewMenu], icon: Codicon.chevronRight, title: localize('editor.action.accessibleViewNext', "Show Next in Accessible View") }); @@ -584,14 +592,7 @@ class AccessibleViewPreviousAction extends Action2 { weight: KeybindingWeight.WorkbenchContrib }, icon: Codicon.chevronLeft, - menu: [{ - id: MenuId.CommandPalette, - group: '', - }, - { - id: MenuId.AccessibleView, - group: 'navigation' - }], + menu: [commandPalette, accessibleViewMenu], title: localize('editor.action.accessibleViewPrevious', "Show Previous in Accessible View") }); } @@ -612,14 +613,7 @@ class AccessibleViewGoToSymbolAction extends Action2 { weight: KeybindingWeight.WorkbenchContrib + 10 }, icon: Codicon.symbolField, - menu: [{ - id: MenuId.CommandPalette, - group: '', - order: 1 - }, { - id: MenuId.AccessibleView, - group: 'navigation' - }], + menu: [commandPalette, accessibleViewMenu], title: localize('editor.action.accessibleViewGoToSymbol', "Go To Symbol in Accessible View") }); } @@ -683,15 +677,12 @@ class AccessibleViewDisableHintAction extends Action2 { weight: KeybindingWeight.WorkbenchContrib }, icon: Codicon.treeFilterClear, - menu: [{ - id: MenuId.CommandPalette, - group: '', - order: 1 - }, - { - id: MenuId.AccessibleView, - group: 'navigation' - }], + menu: [commandPalette, + { + id: MenuId.AccessibleView, + group: 'navigation', + when: ContextKeyExpr.or(accessibleViewIsShown, accessibilityHelpIsShown) + }], title: localize('editor.action.accessibleViewDisableHint', "Disable Accessible View Hint") }); } @@ -710,11 +701,7 @@ class AccessibleViewFocusToolbarAction extends Action2 { primary: KeyMod.Alt | KeyCode.F7, weight: KeybindingWeight.WorkbenchContrib }, - menu: [{ - id: MenuId.CommandPalette, - group: '', - order: 1 - }], + menu: [commandPalette], title: localize('editor.action.accessibleViewFocusToolbar', "Accessible View Focus Toolbar") }); } From 86fcc814a2c16c129c40003dad1973b92cb0141a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Aug 2023 08:29:04 -0700 Subject: [PATCH 44/69] rm some more code --- .../workbench/contrib/accessibility/browser/accessibleView.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index f9d1349395e..f3526f08274 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -383,9 +383,6 @@ class AccessibleView extends Disposable { render: (container) => { container.classList.add('accessible-view-container'); return this._render(accessibleViewHelpProvider, container, true); - }, - onHide: () => { - this._accessiblityHelpIsShown.reset(); } }; // HACK: Delay to allow the context view to hide #186514 From 734d640a12b2ba782d30d20c60ff5ecb2f854a3e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Aug 2023 09:55:13 -0700 Subject: [PATCH 45/69] fix more issues --- .../accessibility/browser/accessibleView.ts | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index f3526f08274..d8c5abc0565 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -143,10 +143,8 @@ class AccessibleView extends Disposable { })); } - show(provider?: IAccessibleContentProvider, symbol?: IAccessibleViewSymbol): void { - if (!provider) { - provider = this._currentProvider; - } + show(provider?: IAccessibleContentProvider, symbol?: IAccessibleViewSymbol, showAccessibleViewHelp?: boolean): void { + provider = provider ?? this._currentProvider; if (!provider) { return; } @@ -154,7 +152,7 @@ class AccessibleView extends Disposable { getAnchor: () => { return { x: (window.innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; }, render: (container) => { container.classList.add('accessible-view-container'); - return this._render(provider!, container); + return this._render(provider!, container, showAccessibleViewHelp); }, onHide: () => { this._currentProvider = undefined; @@ -254,8 +252,9 @@ class AccessibleView extends Disposable { } } - private _render(provider: IAccessibleContentProvider, container: HTMLElement, internal?: boolean): IDisposable { - if (!internal) { + private _render(provider: IAccessibleContentProvider, container: HTMLElement, showAccessibleViewHelp?: boolean): IDisposable { + if (!showAccessibleViewHelp) { + // don't overwrite the current provider this._currentProvider = provider; } this._updateContextKeys(provider, true); @@ -303,15 +302,13 @@ class AccessibleView extends Disposable { if (accessibilityHelpKeybinding) { helpHint = localize('ariaAccessibilityHelp', "Use {0} for accessibility help", accessibilityHelpKeybinding); } - if (helpHint) { - ariaLabel = provider.options.ariaLabel ? localize('helpAriaKb', "{0}, {1}", provider.options.ariaLabel, helpHint) : localize('accessible-view', "Accessible View, {0}", helpHint); - } else { - ariaLabel = provider.options.ariaLabel ? provider.options.ariaLabel : localize('helpAriaNoKb', "Accessible View"); - } } - if (internal) { - ariaLabel = localize('accessibleViewHelp', "Accessible View Help"); + if (helpHint) { + ariaLabel = provider.options.ariaLabel ? localize('helpAriaKb', "{0}, {1}", provider.options.ariaLabel, helpHint) : localize('accessible-view', "Accessible View, {0}", helpHint); + } else { + ariaLabel = provider.options.ariaLabel ? provider.options.ariaLabel : localize('helpAriaNoKb', "Accessible View"); } + this._editorWidget.updateOptions({ ariaLabel }); this._editorWidget.focus(); }); @@ -369,15 +366,20 @@ class AccessibleView extends Disposable { return; } - const previousProvider = Object.assign({}, this._currentProvider); - const accessibleViewHelpProvider = Object.assign({}, this._currentProvider); - accessibleViewHelpProvider.options.type = AccessibleViewType.Help; - accessibleViewHelpProvider.provideContent = () => this._getAccessibleViewHelpDialogContent(accessibleViewHelpProvider); - accessibleViewHelpProvider.onClose = () => { - previousProvider.options.type = AccessibleViewType.View; - this.show(previousProvider); + const currentProvider = Object.assign({}, this._currentProvider); + currentProvider.options = Object.assign({}, currentProvider.options); + const currentProviderHasSymbols = this._currentProvider.options.language === 'markdown' || this._currentProvider.options.language === undefined || !!this._currentProvider.getSymbols; + const accessibleViewHelpProvider: IAccessibleContentProvider = { + provideContent: () => this._getAccessibleViewHelpDialogContent(currentProviderHasSymbols), + onClose: () => this.show(currentProvider), + options: { + ariaLabel: localize('accessibleViewHelp', "Accessible View Help"), + type: AccessibleViewType.Help, + }, + verbositySettingKey: this._currentProvider.verbositySettingKey }; this._contextViewService.hideContextView(); + const delegate: IContextViewDelegate = { getAnchor: () => { return { x: (window.innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; }, render: (container) => { @@ -389,9 +391,9 @@ class AccessibleView extends Disposable { setTimeout(() => this._contextViewService.showContextView(delegate), 100); } - private _getAccessibleViewHelpDialogContent(provider: IAccessibleContentProvider): string { + private _getAccessibleViewHelpDialogContent(providerHasSymbols?: boolean): string { const navigationHint = this._getNavigationHint(); - const goToSymbolHint = this._getGoToSymbolHint(provider); + const goToSymbolHint = this._getGoToSymbolHint(providerHasSymbols); const toolbarHint = this._getToolbarHint(); let hint = localize('intro', "In the accessible view, you can:\n"); if (navigationHint) { @@ -442,11 +444,10 @@ class AccessibleView extends Disposable { return hint; } - private _getGoToSymbolHint(provider: IAccessibleContentProvider): string { + private _getGoToSymbolHint(providerHasSymbols?: boolean): string { const goToSymbolKb = this._keybindingService.lookupKeybinding('editor.action.accessibleViewGoToSymbol')?.getAriaLabel(); - const hasSymbolProvider = provider.options.language === 'markdown' || provider.options.language === undefined || !!provider.getSymbols; let goToSymbolHint = ''; - if (hasSymbolProvider) { + if (providerHasSymbols) { if (goToSymbolKb) { goToSymbolHint = localize('goToSymbolHint', 'Go to a symbol ({0})', goToSymbolKb); } else { From 8f6ea345ff874f151ca626b6b15be20fb39bb8de Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Aug 2023 10:03:37 -0700 Subject: [PATCH 46/69] remove some code --- .../accessibility/browser/accessibleView.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index d8c5abc0565..05643b5c838 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -155,7 +155,9 @@ class AccessibleView extends Disposable { return this._render(provider!, container, showAccessibleViewHelp); }, onHide: () => { - this._currentProvider = undefined; + if (!showAccessibleViewHelp) { + this._currentProvider = undefined; + } } }; this._contextViewService.showContextView(delegate); @@ -379,16 +381,8 @@ class AccessibleView extends Disposable { verbositySettingKey: this._currentProvider.verbositySettingKey }; this._contextViewService.hideContextView(); - - const delegate: IContextViewDelegate = { - getAnchor: () => { return { x: (window.innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; }, - render: (container) => { - container.classList.add('accessible-view-container'); - return this._render(accessibleViewHelpProvider, container, true); - } - }; // HACK: Delay to allow the context view to hide #186514 - setTimeout(() => this._contextViewService.showContextView(delegate), 100); + setTimeout(() => this.show(accessibleViewHelpProvider, undefined, true), 100); } private _getAccessibleViewHelpDialogContent(providerHasSymbols?: boolean): string { From 77e059fc055e773919a72f4b830eb0cda288161f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Aug 2023 10:09:22 -0700 Subject: [PATCH 47/69] split up contrib --- .../browser/accessibility.contribution.ts | 240 +---------------- .../browser/accessibilityContributions.ts | 244 ++++++++++++++++++ 2 files changed, 247 insertions(+), 237 deletions(-) create mode 100644 src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 38fe7b2c978..81deacd41ea 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -3,256 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { AccessibilityHelpNLS } from 'vs/editor/common/standaloneStrings'; -import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode'; -import { localize } from 'vs/nls'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { AccessibilityVerbositySettingId, accessibleViewIsShown, registerAccessibilityConfiguration } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import * as strings from 'vs/base/common/strings'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { registerAccessibilityConfiguration } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileConstants'; -import { ModesHoverController } from 'vs/editor/contrib/hover/browser/hover'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { getNotificationFromContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; -import { IListService, WorkbenchList } from 'vs/platform/list/browser/listService'; -import { NotificationFocusedContext } from 'vs/workbench/common/contextkeys'; -import { IAccessibleViewService, AccessibleViewService, IAccessibleContentProvider, IAccessibleViewOptions, AccessibleViewType, AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; -import { alert } from 'vs/base/browser/ui/aria/aria'; +import { IAccessibleViewService, AccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { UnfocusedViewDimmingContribution } from 'vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution'; +import { EditorAccessibilityHelpContribution, HoverAccessibleViewContribution, NotificationAccessibleViewContribution } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions'; registerAccessibilityConfiguration(); registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed); -class AccessibilityHelpProvider implements IAccessibleContentProvider { - onClose() { - this._editor.focus(); - } - options: IAccessibleViewOptions = { type: AccessibleViewType.Help, ariaLabel: localize('editor-help', "editor accessibility help"), readMoreUrl: 'https://go.microsoft.com/fwlink/?linkid=851010' }; - verbositySettingKey = AccessibilityVerbositySettingId.Editor; - constructor( - private readonly _editor: ICodeEditor, - @IKeybindingService private readonly _keybindingService: IKeybindingService - ) { - } - - private _descriptionForCommand(commandId: string, msg: string, noKbMsg: string): string { - const kb = this._keybindingService.lookupKeybinding(commandId); - if (kb) { - return strings.format(msg, kb.getAriaLabel()); - } - return strings.format(noKbMsg, commandId); - } - - provideContent(): string { - const options = this._editor.getOptions(); - const content = []; - - if (options.get(EditorOption.inDiffEditor)) { - if (options.get(EditorOption.readOnly)) { - content.push(AccessibilityHelpNLS.readonlyDiffEditor); - } else { - content.push(AccessibilityHelpNLS.editableDiffEditor); - } - } else { - if (options.get(EditorOption.readOnly)) { - content.push(AccessibilityHelpNLS.readonlyEditor); - } else { - content.push(AccessibilityHelpNLS.editableEditor); - } - } - - if (options.get(EditorOption.tabFocusMode)) { - content.push(this._descriptionForCommand(ToggleTabFocusModeAction.ID, AccessibilityHelpNLS.tabFocusModeOnMsg, AccessibilityHelpNLS.tabFocusModeOnMsgNoKb)); - } else { - content.push(this._descriptionForCommand(ToggleTabFocusModeAction.ID, AccessibilityHelpNLS.tabFocusModeOffMsg, AccessibilityHelpNLS.tabFocusModeOffMsgNoKb)); - } - return content.join('\n'); - } -} - -class EditorAccessibilityHelpContribution extends Disposable { - static ID: 'editorAccessibilityHelpContribution'; - constructor() { - super(); - this._register(AccessibilityHelpAction.addImplementation(95, 'editor', async accessor => { - const codeEditorService = accessor.get(ICodeEditorService); - const accessibleViewService = accessor.get(IAccessibleViewService); - const instantiationService = accessor.get(IInstantiationService); - const commandService = accessor.get(ICommandService); - let codeEditor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); - if (!codeEditor) { - await commandService.executeCommand(NEW_UNTITLED_FILE_COMMAND_ID); - codeEditor = codeEditorService.getActiveCodeEditor()!; - } - accessibleViewService.show(instantiationService.createInstance(AccessibilityHelpProvider, codeEditor)); - })); - } -} - const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(EditorAccessibilityHelpContribution, LifecyclePhase.Eventually); workbenchRegistry.registerWorkbenchContribution(UnfocusedViewDimmingContribution, LifecyclePhase.Restored); - - -class HoverAccessibleViewContribution extends Disposable { - static ID: 'hoverAccessibleViewContribution'; - private _options: IAccessibleViewOptions = { - ariaLabel: localize('hoverAccessibleView', "Hover Accessible View"), language: 'typescript', type: AccessibleViewType.View - }; - constructor() { - super(); - this._register(AccessibleViewAction.addImplementation(95, 'hover', accessor => { - const accessibleViewService = accessor.get(IAccessibleViewService); - const codeEditorService = accessor.get(ICodeEditorService); - const editor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); - const editorHoverContent = editor ? ModesHoverController.get(editor)?.getWidgetContent() ?? undefined : undefined; - if (!editor || !editorHoverContent) { - return false; - } - this._options.language = editor?.getModel()?.getLanguageId() ?? undefined; - accessibleViewService.show({ - verbositySettingKey: AccessibilityVerbositySettingId.Hover, - provideContent() { return editorHoverContent; }, - onClose() { - ModesHoverController.get(editor)?.focus(); - }, - options: this._options - }); - return true; - }, EditorContextKeys.hoverFocused)); - this._register(AccessibleViewAction.addImplementation(90, 'extension-hover', accessor => { - const accessibleViewService = accessor.get(IAccessibleViewService); - const contextViewService = accessor.get(IContextViewService); - const contextViewElement = contextViewService.getContextViewElement(); - const extensionHoverContent = contextViewElement?.textContent ?? undefined; - const hoverService = accessor.get(IHoverService); - - if (contextViewElement.classList.contains('accessible-view-container') || !extensionHoverContent) { - // The accessible view, itself, uses the context view service to display the text. We don't want to read that. - return false; - } - accessibleViewService.show({ - verbositySettingKey: AccessibilityVerbositySettingId.Hover, - provideContent() { return extensionHoverContent; }, - onClose() { - hoverService.showAndFocusLastHover(); - }, - options: this._options - }); - return true; - })); - this._register(AccessibilityHelpAction.addImplementation(115, 'accessible-view', accessor => { - accessor.get(IAccessibleViewService).showAccessibleViewHelp(); - return true; - }, accessibleViewIsShown)); - } -} - const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(HoverAccessibleViewContribution, LifecyclePhase.Eventually); - - -class NotificationAccessibleViewContribution extends Disposable { - static ID: 'notificationAccessibleViewContribution'; - constructor() { - super(); - this._register(AccessibleViewAction.addImplementation(90, 'notifications', accessor => { - const accessibleViewService = accessor.get(IAccessibleViewService); - const listService = accessor.get(IListService); - const commandService = accessor.get(ICommandService); - - function renderAccessibleView(): boolean { - const notification = getNotificationFromContext(listService); - if (!notification) { - return false; - } - commandService.executeCommand('notifications.showList'); - let notificationIndex: number | undefined; - let length: number | undefined; - const list = listService.lastFocusedList; - if (list instanceof WorkbenchList) { - notificationIndex = list.indexOf(notification); - length = list.length; - } - if (notificationIndex === undefined) { - return false; - } - - function focusList(): void { - commandService.executeCommand('notifications.showList'); - if (list && notificationIndex !== undefined) { - list.domFocus(); - try { - list.setFocus([notificationIndex]); - } catch { } - } - } - const message = notification.message.original.toString(); - if (!message) { - return false; - } - accessibleViewService.show({ - provideContent: () => { - return localize('notification.accessibleView', '{0} Source: {1}', message, notification.source); - }, - onClose(): void { - focusList(); - }, - next(): void { - if (!list) { - return; - } - focusList(); - list.focusNext(); - alertFocusChange(notificationIndex, length, 'next'); - renderAccessibleView(); - }, - previous(): void { - if (!list) { - return; - } - focusList(); - list.focusPrevious(); - alertFocusChange(notificationIndex, length, 'previous'); - renderAccessibleView(); - }, - verbositySettingKey: AccessibilityVerbositySettingId.Notification, - options: { - ariaLabel: localize('notification', "Notification Accessible View"), - type: AccessibleViewType.View - } - }); - return true; - } - return renderAccessibleView(); - }, NotificationFocusedContext)); - } -} - workbenchContributionsRegistry.registerWorkbenchContribution(NotificationAccessibleViewContribution, LifecyclePhase.Eventually); - -export function alertFocusChange(index: number | undefined, length: number | undefined, type: 'next' | 'previous'): void { - if (index === undefined || length === undefined) { - return; - } - const number = index + 1; - - if (type === 'next' && number + 1 <= length) { - alert(`Focused ${number + 1} of ${length}`); - } else if (type === 'previous' && number - 1 > 0) { - alert(`Focused ${number - 1} of ${length}`); - } - return; -} diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts new file mode 100644 index 00000000000..0fe95024b71 --- /dev/null +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts @@ -0,0 +1,244 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/*--------------------------------------------------------------------------------------------- + * 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 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { AccessibilityHelpNLS } from 'vs/editor/common/standaloneStrings'; +import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode'; +import { localize } from 'vs/nls'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { AccessibilityVerbositySettingId, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import * as strings from 'vs/base/common/strings'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileConstants'; +import { ModesHoverController } from 'vs/editor/contrib/hover/browser/hover'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { getNotificationFromContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; +import { IListService, WorkbenchList } from 'vs/platform/list/browser/listService'; +import { NotificationFocusedContext } from 'vs/workbench/common/contextkeys'; +import { IAccessibleViewService, IAccessibleContentProvider, IAccessibleViewOptions, AccessibleViewType, AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { alert } from 'vs/base/browser/ui/aria/aria'; + +export class EditorAccessibilityHelpContribution extends Disposable { + static ID: 'editorAccessibilityHelpContribution'; + constructor() { + super(); + this._register(AccessibilityHelpAction.addImplementation(95, 'editor', async accessor => { + const codeEditorService = accessor.get(ICodeEditorService); + const accessibleViewService = accessor.get(IAccessibleViewService); + const instantiationService = accessor.get(IInstantiationService); + const commandService = accessor.get(ICommandService); + let codeEditor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); + if (!codeEditor) { + await commandService.executeCommand(NEW_UNTITLED_FILE_COMMAND_ID); + codeEditor = codeEditorService.getActiveCodeEditor()!; + } + accessibleViewService.show(instantiationService.createInstance(AccessibilityHelpProvider, codeEditor)); + })); + } +} + +class AccessibilityHelpProvider implements IAccessibleContentProvider { + onClose() { + this._editor.focus(); + } + options: IAccessibleViewOptions = { type: AccessibleViewType.Help, ariaLabel: localize('editor-help', "editor accessibility help"), readMoreUrl: 'https://go.microsoft.com/fwlink/?linkid=851010' }; + verbositySettingKey = AccessibilityVerbositySettingId.Editor; + constructor( + private readonly _editor: ICodeEditor, + @IKeybindingService private readonly _keybindingService: IKeybindingService + ) { + } + + private _descriptionForCommand(commandId: string, msg: string, noKbMsg: string): string { + const kb = this._keybindingService.lookupKeybinding(commandId); + if (kb) { + return strings.format(msg, kb.getAriaLabel()); + } + return strings.format(noKbMsg, commandId); + } + + provideContent(): string { + const options = this._editor.getOptions(); + const content = []; + + if (options.get(EditorOption.inDiffEditor)) { + if (options.get(EditorOption.readOnly)) { + content.push(AccessibilityHelpNLS.readonlyDiffEditor); + } else { + content.push(AccessibilityHelpNLS.editableDiffEditor); + } + } else { + if (options.get(EditorOption.readOnly)) { + content.push(AccessibilityHelpNLS.readonlyEditor); + } else { + content.push(AccessibilityHelpNLS.editableEditor); + } + } + + if (options.get(EditorOption.tabFocusMode)) { + content.push(this._descriptionForCommand(ToggleTabFocusModeAction.ID, AccessibilityHelpNLS.tabFocusModeOnMsg, AccessibilityHelpNLS.tabFocusModeOnMsgNoKb)); + } else { + content.push(this._descriptionForCommand(ToggleTabFocusModeAction.ID, AccessibilityHelpNLS.tabFocusModeOffMsg, AccessibilityHelpNLS.tabFocusModeOffMsgNoKb)); + } + return content.join('\n'); + } +} + +export class HoverAccessibleViewContribution extends Disposable { + static ID: 'hoverAccessibleViewContribution'; + private _options: IAccessibleViewOptions = { + ariaLabel: localize('hoverAccessibleView', "Hover Accessible View"), language: 'typescript', type: AccessibleViewType.View + }; + constructor() { + super(); + this._register(AccessibleViewAction.addImplementation(95, 'hover', accessor => { + const accessibleViewService = accessor.get(IAccessibleViewService); + const codeEditorService = accessor.get(ICodeEditorService); + const editor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); + const editorHoverContent = editor ? ModesHoverController.get(editor)?.getWidgetContent() ?? undefined : undefined; + if (!editor || !editorHoverContent) { + return false; + } + this._options.language = editor?.getModel()?.getLanguageId() ?? undefined; + accessibleViewService.show({ + verbositySettingKey: AccessibilityVerbositySettingId.Hover, + provideContent() { return editorHoverContent; }, + onClose() { + ModesHoverController.get(editor)?.focus(); + }, + options: this._options + }); + return true; + }, EditorContextKeys.hoverFocused)); + this._register(AccessibleViewAction.addImplementation(90, 'extension-hover', accessor => { + const accessibleViewService = accessor.get(IAccessibleViewService); + const contextViewService = accessor.get(IContextViewService); + const contextViewElement = contextViewService.getContextViewElement(); + const extensionHoverContent = contextViewElement?.textContent ?? undefined; + const hoverService = accessor.get(IHoverService); + + if (contextViewElement.classList.contains('accessible-view-container') || !extensionHoverContent) { + // The accessible view, itself, uses the context view service to display the text. We don't want to read that. + return false; + } + accessibleViewService.show({ + verbositySettingKey: AccessibilityVerbositySettingId.Hover, + provideContent() { return extensionHoverContent; }, + onClose() { + hoverService.showAndFocusLastHover(); + }, + options: this._options + }); + return true; + })); + this._register(AccessibilityHelpAction.addImplementation(115, 'accessible-view', accessor => { + accessor.get(IAccessibleViewService).showAccessibleViewHelp(); + return true; + }, accessibleViewIsShown)); + } +} + +export class NotificationAccessibleViewContribution extends Disposable { + static ID: 'notificationAccessibleViewContribution'; + constructor() { + super(); + this._register(AccessibleViewAction.addImplementation(90, 'notifications', accessor => { + const accessibleViewService = accessor.get(IAccessibleViewService); + const listService = accessor.get(IListService); + const commandService = accessor.get(ICommandService); + + function renderAccessibleView(): boolean { + const notification = getNotificationFromContext(listService); + if (!notification) { + return false; + } + commandService.executeCommand('notifications.showList'); + let notificationIndex: number | undefined; + let length: number | undefined; + const list = listService.lastFocusedList; + if (list instanceof WorkbenchList) { + notificationIndex = list.indexOf(notification); + length = list.length; + } + if (notificationIndex === undefined) { + return false; + } + + function focusList(): void { + commandService.executeCommand('notifications.showList'); + if (list && notificationIndex !== undefined) { + list.domFocus(); + try { + list.setFocus([notificationIndex]); + } catch { } + } + } + const message = notification.message.original.toString(); + if (!message) { + return false; + } + accessibleViewService.show({ + provideContent: () => { + return localize('notification.accessibleView', '{0} Source: {1}', message, notification.source); + }, + onClose(): void { + focusList(); + }, + next(): void { + if (!list) { + return; + } + focusList(); + list.focusNext(); + alertFocusChange(notificationIndex, length, 'next'); + renderAccessibleView(); + }, + previous(): void { + if (!list) { + return; + } + focusList(); + list.focusPrevious(); + alertFocusChange(notificationIndex, length, 'previous'); + renderAccessibleView(); + }, + verbositySettingKey: AccessibilityVerbositySettingId.Notification, + options: { + ariaLabel: localize('notification', "Notification Accessible View"), + type: AccessibleViewType.View + } + }); + return true; + } + return renderAccessibleView(); + }, NotificationFocusedContext)); + } +} + +export function alertFocusChange(index: number | undefined, length: number | undefined, type: 'next' | 'previous'): void { + if (index === undefined || length === undefined) { + return; + } + const number = index + 1; + + if (type === 'next' && number + 1 <= length) { + alert(`Focused ${number + 1} of ${length}`); + } else if (type === 'previous' && number - 1 > 0) { + alert(`Focused ${number - 1} of ${length}`); + } + return; +} + From 82f6dd4fa9e74c4e05e891598b837c0fe012ae62 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Aug 2023 10:12:12 -0700 Subject: [PATCH 48/69] accessible view actions --- .../browser/accessibleViewActions.ts | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts new file mode 100644 index 00000000000..e68fc303fcc --- /dev/null +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts @@ -0,0 +1,174 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Codicon } from 'vs/base/common/codicons'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { Command, MultiCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { localize } from 'vs/nls'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { accessibilityHelpIsShown, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; + +const accessibleViewMenu = { + id: MenuId.AccessibleView, + group: 'navigation', + when: accessibleViewIsShown +}; +const commandPalette = { + id: MenuId.CommandPalette, + group: '', + order: 1 +}; +class AccessibleViewNextAction extends Action2 { + constructor() { + super({ + id: 'editor.action.accessibleViewNext', + precondition: accessibleViewIsShown, + keybinding: { + primary: KeyMod.Alt | KeyCode.BracketRight, + weight: KeybindingWeight.WorkbenchContrib + }, + menu: [commandPalette, accessibleViewMenu], + icon: Codicon.chevronRight, + title: localize('editor.action.accessibleViewNext', "Show Next in Accessible View") + }); + } + run(accessor: ServicesAccessor): void { + accessor.get(IAccessibleViewService).next(); + } +} +registerAction2(AccessibleViewNextAction); + + +class AccessibleViewPreviousAction extends Action2 { + constructor() { + super({ + id: 'editor.action.accessibleViewPrevious', + precondition: accessibleViewIsShown, + keybinding: { + primary: KeyMod.Alt | KeyCode.BracketLeft, + weight: KeybindingWeight.WorkbenchContrib + }, + icon: Codicon.chevronLeft, + menu: [commandPalette, accessibleViewMenu], + title: localize('editor.action.accessibleViewPrevious', "Show Previous in Accessible View") + }); + } + run(accessor: ServicesAccessor): void { + accessor.get(IAccessibleViewService).previous(); + } +} +registerAction2(AccessibleViewPreviousAction); + + +class AccessibleViewGoToSymbolAction extends Action2 { + constructor() { + super({ + id: 'editor.action.accessibleViewGoToSymbol', + precondition: accessibleViewIsShown, + keybinding: { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO, + weight: KeybindingWeight.WorkbenchContrib + 10 + }, + icon: Codicon.symbolField, + menu: [commandPalette, accessibleViewMenu], + title: localize('editor.action.accessibleViewGoToSymbol', "Go To Symbol in Accessible View") + }); + } + run(accessor: ServicesAccessor): void { + accessor.get(IAccessibleViewService).goToSymbol(); + } +} +registerAction2(AccessibleViewGoToSymbolAction); + +function registerCommand(command: T): T { + command.register(); + return command; +} + +export const AccessibilityHelpAction = registerCommand(new MultiCommand({ + id: 'editor.action.accessibilityHelp', + precondition: undefined, + kbOpts: { + primary: KeyMod.Alt | KeyCode.F1, + weight: KeybindingWeight.WorkbenchContrib, + linux: { + primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F1, + secondary: [KeyMod.Alt | KeyCode.F1] + } + }, + menuOpts: [{ + menuId: MenuId.CommandPalette, + group: '', + title: localize('editor.action.accessibilityHelp', "Open Accessibility Help"), + order: 1 + }], +})); + + +export const AccessibleViewAction = registerCommand(new MultiCommand({ + id: 'editor.action.accessibleView', + precondition: undefined, + kbOpts: { + primary: KeyMod.Alt | KeyCode.F2, + weight: KeybindingWeight.WorkbenchContrib, + linux: { + primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F2, + secondary: [KeyMod.Alt | KeyCode.F2] + } + }, + menuOpts: [{ + menuId: MenuId.CommandPalette, + group: '', + title: localize('editor.action.accessibleView', "Open Accessible View"), + order: 1 + }], +})); + +class AccessibleViewDisableHintAction extends Action2 { + constructor() { + super({ + id: 'editor.action.accessibleViewDisableHint', + keybinding: { + when: ContextKeyExpr.or(accessibleViewIsShown, accessibilityHelpIsShown), + primary: KeyMod.Alt | KeyCode.F6, + weight: KeybindingWeight.WorkbenchContrib + }, + icon: Codicon.treeFilterClear, + menu: [commandPalette, + { + id: MenuId.AccessibleView, + group: 'navigation', + when: ContextKeyExpr.or(accessibleViewIsShown, accessibilityHelpIsShown) + }], + title: localize('editor.action.accessibleViewDisableHint', "Disable Accessible View Hint") + }); + } + run(accessor: ServicesAccessor): void { + accessor.get(IAccessibleViewService).disableHint(); + } +} +registerAction2(AccessibleViewDisableHintAction); + +class AccessibleViewFocusToolbarAction extends Action2 { + constructor() { + super({ + id: 'editor.action.accessibleViewFocusToolbar', + keybinding: { + when: accessibleViewIsShown, + primary: KeyMod.Alt | KeyCode.F7, + weight: KeybindingWeight.WorkbenchContrib + }, + menu: [commandPalette], + title: localize('editor.action.accessibleViewFocusToolbar', "Accessible View Focus Toolbar") + }); + } + run(accessor: ServicesAccessor): void { + accessor.get(IAccessibleViewService).focusToolbar(); + } +} +registerAction2(AccessibleViewFocusToolbarAction); From 0ca15b29b422f037cc516865a22dc35f42476456 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Aug 2023 10:20:38 -0700 Subject: [PATCH 49/69] Fix compile error --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index bcd455bd275..c3bd91e88fb 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -42,12 +42,12 @@ import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { alertFocusChange } from 'vs/workbench/contrib/accessibility/browser/accessibility.contribution'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { ChatWelcomeMessageModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { ChatProviderService, IChatProviderService } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; +import { alertFocusChange } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); From 97728202328aa60392aba151ab1597cd8b3feca0 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Wed, 9 Aug 2023 20:18:48 +0200 Subject: [PATCH 50/69] Restore version check (#190042) --- .../common/extensionsProfileScannerService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts index d3c27750a2c..30e749ac312 100644 --- a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts @@ -127,8 +127,8 @@ export abstract class AbstractExtensionsProfileScannerService extends Disposable await this.withProfileExtensions(profileLocation, profileExtensions => { const result: IScannedProfileExtension[] = []; for (const extension of profileExtensions) { - if (extensions.some(([e]) => areSameExtensions(e.identifier, extension.identifier))) { - // Remove the existing extension + if (extensions.some(([e]) => areSameExtensions(e.identifier, extension.identifier) && e.manifest.version !== extension.version)) { + // Remove the existing extension with different version extensionsToRemove.push(extension); } else { result.push(extension); From 45cd7cab89e66934a5c43cab7c169c4db4a3fd7f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 9 Aug 2023 11:32:40 -0700 Subject: [PATCH 51/69] Add terminal/context menu contribution - Adds the API - Consolidates the editor and view terminal menus - Removes the Show Tabs entry as tabs are very old now Part of #79034 --- .../contrib/terminal/browser/terminal.ts | 1 - .../terminal/browser/terminalActions.ts | 35 +++++++++---------- .../terminal/browser/terminalEditor.ts | 2 +- .../terminal/browser/terminalGroupService.ts | 10 ++---- .../contrib/terminal/browser/terminalMenus.ts | 15 ++------ .../contrib/terminal/common/terminal.ts | 2 +- .../actions/common/menusExtensionPoint.ts | 6 ++++ .../common/extensionsApiProposals.ts | 1 + .../test/browser/workbenchTestServices.ts | 1 - .../vscode.proposed.terminalContextMenu.d.ts | 12 +++++++ 10 files changed, 42 insertions(+), 43 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.terminalContextMenu.d.ts diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index ec146c02cf6..b31122901ec 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -370,7 +370,6 @@ export interface ITerminalGroupService extends ITerminalInstanceHost { hidePanel(): void; focusTabs(): void; focusHover(): void; - showTabs(): void; updateVisibility(): void; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 5932d72db82..0f5762f181b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -317,14 +317,6 @@ export function registerTerminalActions() { } }); - registerTerminalAction({ - id: TerminalCommandId.ShowTabs, - title: { value: localize('workbench.action.terminal.showTabs', "Show Tabs"), original: 'Show Tabs' }, - f1: false, - precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (c) => c.groupService.showTabs() - }); - registerTerminalAction({ id: TerminalCommandId.FocusPreviousPane, title: { value: localize('workbench.action.terminal.focusPreviousPane', "Focus Previous Terminal in Terminal Group"), original: 'Focus Previous Terminal in Terminal Group' }, @@ -1241,21 +1233,28 @@ export function registerTerminalActions() { } }); + async function killInstance(c: ITerminalServicesCollection, instance: ITerminalInstance | undefined): Promise { + if (!instance) { + return; + } + await c.service.safeDisposeTerminal(instance); + if (c.groupService.instances.length > 0) { + await c.groupService.showPanel(true); + } + } registerTerminalAction({ id: TerminalCommandId.Kill, title: { value: localize('workbench.action.terminal.kill', "Kill the Active Terminal Instance"), original: 'Kill the Active Terminal Instance' }, precondition: ContextKeyExpr.or(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen), icon: killTerminalIcon, - run: async (c) => { - const instance = c.groupService.activeInstance; - if (!instance) { - return; - } - await c.service.safeDisposeTerminal(instance); - if (c.groupService.instances.length > 0) { - await c.groupService.showPanel(true); - } - } + run: async (c) => killInstance(c, c.groupService.activeInstance) + }); + registerTerminalAction({ + id: TerminalCommandId.KillViewOrEditor, + title: terminalStrings.kill, + f1: false, // This is an internal command used for context menus + precondition: ContextKeyExpr.or(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen), + run: async (c) => killInstance(c, c.service.activeInstance) }); registerTerminalAction({ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts index eb04b5e2667..a7bd0423aad 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts @@ -62,7 +62,7 @@ export class TerminalEditor extends EditorPane { ) { super(terminalEditorId, telemetryService, themeService, storageService); this._dropdownMenu = this._register(menuService.createMenu(MenuId.TerminalNewDropdownContext, contextKeyService)); - this._instanceMenu = this._register(menuService.createMenu(MenuId.TerminalEditorInstanceContext, contextKeyService)); + this._instanceMenu = this._register(menuService.createMenu(MenuId.TerminalInstanceContext, contextKeyService)); } override async setInput(newInput: TerminalEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts index 990072a3570..80de05a407a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts @@ -8,10 +8,9 @@ import { timeout } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IShellLaunchConfig, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { IShellLaunchConfig } from 'vs/platform/terminal/common/terminal'; import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { ITerminalGroup, ITerminalGroupService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalGroup } from 'vs/workbench/contrib/terminal/browser/terminalGroup'; @@ -60,8 +59,7 @@ export class TerminalGroupService extends Disposable implements ITerminalGroupSe @IContextKeyService private _contextKeyService: IContextKeyService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IViewsService private readonly _viewsService: IViewsService, - @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, - @IConfigurationService private readonly _configurationService: IConfigurationService, + @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService ) { super(); @@ -83,10 +81,6 @@ export class TerminalGroupService extends Disposable implements ITerminalGroupSe } } - showTabs() { - this._configurationService.updateValue(TerminalSettingId.TabsEnabled, true); - } - get activeGroup(): ITerminalGroup | undefined { if (this.activeGroupIndex < 0 || this.activeGroupIndex >= this.groups.length) { return undefined; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index eddc9648b64..07256a30de5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -113,8 +113,8 @@ export function setupTerminalMenus(): void { id: MenuId.TerminalInstanceContext, item: { command: { - id: TerminalCommandId.Kill, - title: terminalStrings.kill.value + id: TerminalCommandId.KillViewOrEditor, + title: terminalStrings.kill.value, }, group: ContextMenuGroup.Kill } @@ -162,17 +162,6 @@ export function setupTerminalMenus(): void { group: ContextMenuGroup.Clear, } }, - { - id: MenuId.TerminalInstanceContext, - item: { - command: { - id: TerminalCommandId.ShowTabs, - title: localize('workbench.action.terminal.showsTabs', "Show Tabs") - }, - when: ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`), - group: ContextMenuGroup.Config - } - }, { id: MenuId.TerminalInstanceContext, item: { diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 219ca791ee1..0482528aefd 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -385,6 +385,7 @@ export const enum TerminalCommandId { FindPrevious = 'workbench.action.terminal.findPrevious', Toggle = 'workbench.action.terminal.toggleTerminal', Kill = 'workbench.action.terminal.kill', + KillViewOrEditor = 'workbench.action.terminal.killViewOrEditor', KillEditor = 'workbench.action.terminal.killEditor', KillInstance = 'workbench.action.terminal.killInstance', KillAll = 'workbench.action.terminal.killAll', @@ -426,7 +427,6 @@ export const enum TerminalCommandId { Join = 'workbench.action.terminal.join', Relaunch = 'workbench.action.terminal.relaunch', FocusPreviousPane = 'workbench.action.terminal.focusPreviousPane', - ShowTabs = 'workbench.action.terminal.showTabs', CreateTerminalEditor = 'workbench.action.createTerminalEditor', CreateTerminalEditorSameGroup = 'workbench.action.createTerminalEditorSameGroup', CreateTerminalEditorSide = 'workbench.action.createTerminalEditorSide', diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 45c0c9b5b9e..2ce2a66aa15 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -150,6 +150,12 @@ const apiMenus: IAPIMenu[] = [ description: localize('menus.statusBarRemoteIndicator', "The remote indicator menu in the status bar"), supportsSubmenus: false }, + { + key: 'terminal/context', + id: MenuId.TerminalInstanceContext, + description: localize('menus.terminalContext', "The terminal context menu"), + proposed: 'terminalContextMenu' + }, { key: 'view/title', id: MenuId.ViewTitle, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index c466d184f66..b42edd13f55 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -81,6 +81,7 @@ export const allApiProposals = Object.freeze({ tabInputTextMerge: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts', taskPresentationGroup: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts', telemetry: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetry.d.ts', + terminalContextMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalContextMenu.d.ts', terminalDataWriteEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts', terminalDimensions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts', terminalQuickFixProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalQuickFixProvider.d.ts', diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index a0d2d45b753..25804680d05 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1867,7 +1867,6 @@ export class TestTerminalGroupService implements ITerminalGroupService { showPanel(focus?: boolean): Promise { throw new Error('Method not implemented.'); } hidePanel(): void { throw new Error('Method not implemented.'); } focusTabs(): void { throw new Error('Method not implemented.'); } - showTabs(): void { throw new Error('Method not implemented.'); } focusHover(): void { throw new Error('Method not implemented.'); } setActiveInstance(instance: ITerminalInstance): void { throw new Error('Method not implemented.'); } focusActiveInstance(): Promise { throw new Error('Method not implemented.'); } diff --git a/src/vscode-dts/vscode.proposed.terminalContextMenu.d.ts b/src/vscode-dts/vscode.proposed.terminalContextMenu.d.ts new file mode 100644 index 00000000000..768600e79a9 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.terminalContextMenu.d.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/79034 + + // This proposal doesn't have any API changes, only contributions + +} From 6c32fd48c5637a91edd06ffabf8b4f609faa1154 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 9 Aug 2023 11:42:07 -0700 Subject: [PATCH 52/69] Move `overscroll-behavior` to `body` (#189000) Fixes #182013 Looks like this needs to be set on the body, not the html element --- .../workbench/contrib/webview/browser/pre/index-no-csp.html | 2 +- src/vs/workbench/contrib/webview/browser/pre/index.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html index b51db4053c9..0cc07c2f123 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html @@ -85,11 +85,11 @@ defaultStyles.id = '_defaultStyles'; defaultStyles.textContent = ` html { - overscroll-behavior-x: none; scrollbar-color: var(--vscode-scrollbarSlider-background) var(--vscode-editor-background); } body { + overscroll-behavior-x: none; background-color: transparent; color: var(--vscode-editor-foreground); font-family: var(--vscode-font-family); diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index c402590f81a..a6c387295d6 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -5,7 +5,7 @@ + content="default-src 'none'; script-src 'sha256-QA1gXilHYAUFCvp7MpjgcmyBCFzSKV0SpiecMU8aUVc=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> Date: Wed, 9 Aug 2023 11:55:15 -0700 Subject: [PATCH 53/69] Add terminal/title/context menu contribution - Adds the contribution - Consolidates inline tab and tab list menu - Renamed '...Instance' commands to be less ambiguous Part of #79034 --- src/vs/platform/actions/common/actions.ts | 1 - .../contrib/terminal/browser/terminal.ts | 4 + .../terminal/browser/terminalActions.ts | 27 +++-- .../terminal/browser/terminalGroupService.ts | 2 + .../contrib/terminal/browser/terminalMenus.ts | 103 +++--------------- .../terminal/browser/terminalTabbedView.ts | 3 + .../terminal/browser/terminalTabsList.ts | 4 +- .../contrib/terminal/browser/terminalView.ts | 3 +- .../contrib/terminal/common/terminal.ts | 18 +-- .../actions/common/menusExtensionPoint.ts | 6 + 10 files changed, 59 insertions(+), 112 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 3139a073b5d..feec58c61bf 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -173,7 +173,6 @@ export class MenuId { static readonly TerminalNewDropdownContext = new MenuId('TerminalNewDropdownContext'); static readonly TerminalTabContext = new MenuId('TerminalTabContext'); static readonly TerminalTabEmptyAreaContext = new MenuId('TerminalTabEmptyAreaContext'); - static readonly TerminalInlineTabContext = new MenuId('TerminalInlineTabContext'); static readonly WebviewContext = new MenuId('WebviewContext'); static readonly InlineCompletionsActions = new MenuId('InlineCompletionsActions'); static readonly NewFile = new MenuId('NewFile'); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index b31122901ec..46e802a0e8c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -331,6 +331,10 @@ export interface ITerminalGroupService extends ITerminalInstanceHost { readonly groups: readonly ITerminalGroup[]; activeGroup: ITerminalGroup | undefined; readonly activeGroupIndex: number; + /** + * Gets or sets the last accessed menu, this is used to select the instance(s) for menu actions. + */ + lastAccessedMenu: 'inline-tab' | 'tab-list'; readonly onDidChangeActiveGroup: Event; readonly onDidDisposeGroup: Event; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 0f5762f181b..edfb7c21c4a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -289,7 +289,7 @@ export function registerTerminalActions() { }); registerTerminalAction({ - id: TerminalCommandId.MoveToEditorInstance, + id: TerminalCommandId.MoveToEditorActiveTab, title: terminalStrings.moveToEditor, f1: false, precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen), @@ -700,7 +700,7 @@ export function registerTerminalActions() { }); registerTerminalAction({ - id: TerminalCommandId.ChangeIconInstance, + id: TerminalCommandId.ChangeIconActiveTab, title: terminalStrings.changeIcon, f1: false, precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.tabsSingularSelection), @@ -723,7 +723,7 @@ export function registerTerminalActions() { }); registerTerminalAction({ - id: TerminalCommandId.ChangeColorInstance, + id: TerminalCommandId.ChangeColorActiveTab, title: terminalStrings.changeColor, f1: false, precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.tabsSingularSelection), @@ -746,7 +746,7 @@ export function registerTerminalActions() { }); registerTerminalAction({ - id: TerminalCommandId.RenameInstance, + id: TerminalCommandId.RenameActiveTab, title: terminalStrings.rename, f1: false, keybinding: { @@ -1044,7 +1044,7 @@ export function registerTerminalActions() { }); registerTerminalAction({ - id: TerminalCommandId.SplitInstance, + id: TerminalCommandId.SplitActiveTab, title: terminalStrings.split, f1: false, keybinding: { @@ -1079,7 +1079,7 @@ export function registerTerminalActions() { }); registerTerminalAction({ - id: TerminalCommandId.UnsplitInstance, + id: TerminalCommandId.UnsplitActiveTab, title: terminalStrings.unsplit, f1: false, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -1097,7 +1097,7 @@ export function registerTerminalActions() { }); registerTerminalAction({ - id: TerminalCommandId.JoinInstance, + id: TerminalCommandId.JoinActiveTab, title: { value: localize('workbench.action.terminal.joinInstance', "Join Terminals"), original: 'Join Terminals' }, precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.tabsSingularSelection.toNegated()), run: async (c, accessor) => { @@ -1285,7 +1285,7 @@ export function registerTerminalActions() { }); registerTerminalAction({ - id: TerminalCommandId.KillInstance, + id: TerminalCommandId.KillActiveTab, title: terminalStrings.kill, f1: false, precondition: ContextKeyExpr.or(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen), @@ -1378,7 +1378,7 @@ export function registerTerminalActions() { }); registerTerminalAction({ - id: TerminalCommandId.SizeToContentWidthInstance, + id: TerminalCommandId.SizeToContentWidthActiveTab, title: terminalStrings.toggleSizeToContentWidth, f1: false, precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.focus), @@ -1604,6 +1604,15 @@ interface IRemoteTerminalPick extends IQuickPickItem { function getSelectedInstances(accessor: ServicesAccessor): ITerminalInstance[] | undefined { const listService = accessor.get(IListService); const terminalService = accessor.get(ITerminalService); + const terminalGroupService = accessor.get(ITerminalGroupService); + + // Get inline tab instance + if (terminalGroupService.lastAccessedMenu === 'inline-tab') { + const instance = terminalGroupService.activeInstance; + return instance ? [terminalGroupService.activeInstance] : undefined; + } + + // Get tab list instance if (!listService.lastFocusedList?.getSelection()) { return undefined; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts index 80de05a407a..a77c59de57b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts @@ -28,6 +28,8 @@ export class TerminalGroupService extends Disposable implements ITerminalGroupSe return this.groups.reduce((p, c) => p.concat(c.terminalInstances), [] as ITerminalInstance[]); } + lastAccessedMenu: 'inline-tab' | 'tab-list' = 'inline-tab'; + private _terminalGroupCountContextKey: IContextKey; private _container: HTMLElement | undefined; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index 07256a30de5..e0beb9ec636 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -552,90 +552,13 @@ export function setupTerminalMenus(): void { ] ); - MenuRegistry.appendMenuItems( - [ - { - id: MenuId.TerminalInlineTabContext, - item: { - command: { - id: TerminalCommandId.Split, - title: terminalStrings.split.value - }, - group: ContextMenuGroup.Create, - order: 1 - } - }, - { - id: MenuId.TerminalInlineTabContext, - item: { - command: { - id: TerminalCommandId.MoveToEditor, - title: terminalStrings.moveToEditor.value - }, - group: ContextMenuGroup.Create, - order: 2 - } - }, - { - id: MenuId.TerminalInlineTabContext, - item: { - command: { - id: TerminalCommandId.ChangeIconPanel, - title: terminalStrings.changeIcon.value - }, - group: ContextMenuGroup.Edit - } - }, - { - id: MenuId.TerminalInlineTabContext, - item: { - command: { - id: TerminalCommandId.ChangeColorPanel, - title: terminalStrings.changeColor.value - }, - group: ContextMenuGroup.Edit - } - }, - { - id: MenuId.TerminalInlineTabContext, - item: { - command: { - id: TerminalCommandId.RenamePanel, - title: terminalStrings.rename.value - }, - group: ContextMenuGroup.Edit - } - }, - { - id: MenuId.TerminalInlineTabContext, - item: { - command: { - id: TerminalCommandId.SizeToContentWidthInstance, - title: localize('workbench.action.terminal.sizeToContentWidthInstance', "Toggle Size to Content Width") - }, - group: ContextMenuGroup.Edit - } - }, - { - id: MenuId.TerminalInlineTabContext, - item: { - command: { - id: TerminalCommandId.Kill, - title: terminalStrings.kill.value - }, - group: ContextMenuGroup.Kill - } - } - ] - ); - MenuRegistry.appendMenuItems( [ { id: MenuId.TerminalTabContext, item: { command: { - id: TerminalCommandId.SplitInstance, + id: TerminalCommandId.SplitActiveTab, title: terminalStrings.split.value, }, group: ContextMenuGroup.Create, @@ -646,7 +569,7 @@ export function setupTerminalMenus(): void { id: MenuId.TerminalTabContext, item: { command: { - id: TerminalCommandId.MoveToEditorInstance, + id: TerminalCommandId.MoveToEditorActiveTab, title: terminalStrings.moveToEditor.value }, group: ContextMenuGroup.Create, @@ -657,7 +580,7 @@ export function setupTerminalMenus(): void { id: MenuId.TerminalTabContext, item: { command: { - id: TerminalCommandId.RenameInstance, + id: TerminalCommandId.RenameActiveTab, title: localize('workbench.action.terminal.renameInstance', "Rename...") }, group: ContextMenuGroup.Edit @@ -667,7 +590,7 @@ export function setupTerminalMenus(): void { id: MenuId.TerminalTabContext, item: { command: { - id: TerminalCommandId.ChangeIconInstance, + id: TerminalCommandId.ChangeIconActiveTab, title: localize('workbench.action.terminal.changeIcon', "Change Icon...") }, group: ContextMenuGroup.Edit @@ -677,7 +600,7 @@ export function setupTerminalMenus(): void { id: MenuId.TerminalTabContext, item: { command: { - id: TerminalCommandId.ChangeColorInstance, + id: TerminalCommandId.ChangeColorActiveTab, title: localize('workbench.action.terminal.changeColor', "Change Color...") }, group: ContextMenuGroup.Edit @@ -687,7 +610,7 @@ export function setupTerminalMenus(): void { id: MenuId.TerminalTabContext, item: { command: { - id: TerminalCommandId.SizeToContentWidthInstance, + id: TerminalCommandId.SizeToContentWidthActiveTab, title: localize('workbench.action.terminal.sizeToContentWidthInstance', "Toggle Size to Content Width") }, group: ContextMenuGroup.Edit @@ -696,30 +619,30 @@ export function setupTerminalMenus(): void { { id: MenuId.TerminalTabContext, item: { - group: ContextMenuGroup.Config, command: { - id: TerminalCommandId.JoinInstance, + id: TerminalCommandId.JoinActiveTab, title: localize('workbench.action.terminal.joinInstance', "Join Terminals") }, - when: TerminalContextKeys.tabsSingularSelection.toNegated() + when: TerminalContextKeys.tabsSingularSelection.toNegated(), + group: ContextMenuGroup.Config } }, { id: MenuId.TerminalTabContext, item: { - group: ContextMenuGroup.Config, command: { - id: TerminalCommandId.UnsplitInstance, + id: TerminalCommandId.UnsplitActiveTab, title: terminalStrings.unsplit.value }, - when: ContextKeyExpr.and(TerminalContextKeys.tabsSingularSelection, TerminalContextKeys.splitTerminal) + when: ContextKeyExpr.and(TerminalContextKeys.tabsSingularSelection, TerminalContextKeys.splitTerminal), + group: ContextMenuGroup.Config } }, { id: MenuId.TerminalTabContext, item: { command: { - id: TerminalCommandId.KillInstance, + id: TerminalCommandId.KillActiveTab, title: terminalStrings.kill.value }, group: ContextMenuGroup.Kill, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts index 1251fef88ee..24930c1e40b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts @@ -403,6 +403,9 @@ export class TerminalTabbedView extends Disposable { } if (!this._cancelContextMenu) { const emptyList = this._tabList.getFocus().length === 0; + if (!emptyList) { + this._terminalGroupService.lastAccessedMenu = 'tab-list'; + } openContextMenu(event, this._parentElement, emptyList ? this._tabsListEmptyMenu : this._tabsListMenu, this._contextMenuService, emptyList ? this._getTabActions() : undefined); } event.preventDefault(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 4a3fb064e99..960e08d84bf 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -480,12 +480,12 @@ class TerminalTabsRenderer implements IListRenderer { + new Action(TerminalCommandId.SplitActiveTab, terminalStrings.split.short, ThemeIcon.asClassName(Codicon.splitHorizontal), true, async () => { this._runForSelectionOrInstance(instance, async e => { this._terminalService.createTerminal({ location: { parentTerminal: e } }); }); }), - new Action(TerminalCommandId.KillInstance, terminalStrings.kill.short, ThemeIcon.asClassName(Codicon.trashcan), true, async () => { + new Action(TerminalCommandId.KillActiveTab, terminalStrings.kill.short, ThemeIcon.asClassName(Codicon.trashcan), true, async () => { this._runForSelectionOrInstance(instance, e => this._terminalService.safeDisposeTerminal(e)); }) ]; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index c1a38a3f014..0bb48908910 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -99,7 +99,7 @@ export class TerminalViewPane extends ViewPane { } })); this._dropdownMenu = this._register(this._menuService.createMenu(MenuId.TerminalNewDropdownContext, this._contextKeyService)); - this._singleTabMenu = this._register(this._menuService.createMenu(MenuId.TerminalInlineTabContext, this._contextKeyService)); + this._singleTabMenu = this._register(this._menuService.createMenu(MenuId.TerminalTabContext, this._contextKeyService)); this._register(this._terminalProfileService.onDidChangeAvailableProfiles(profiles => this._updateTabActionBar(profiles))); this._viewShowing = TerminalContextKeys.viewShowing.bindTo(this._contextKeyService); this._register(this.onDidChangeBodyVisibility(e => { @@ -423,6 +423,7 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { } override async onClick(event: MouseEvent): Promise { + this._terminalGroupService.lastAccessedMenu = 'inline-tab'; if (event.altKey && this._menuItemAction.alt) { this._commandService.executeCommand(this._menuItemAction.alt.id, { target: TerminalLocation.Panel } as ICreateTerminalOptions); } else { diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 0482528aefd..d7dd9b9354d 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -387,7 +387,7 @@ export const enum TerminalCommandId { Kill = 'workbench.action.terminal.kill', KillViewOrEditor = 'workbench.action.terminal.killViewOrEditor', KillEditor = 'workbench.action.terminal.killEditor', - KillInstance = 'workbench.action.terminal.killInstance', + KillActiveTab = 'workbench.action.terminal.killActiveTab', KillAll = 'workbench.action.terminal.killAll', QuickKill = 'workbench.action.terminal.quickKill', ConfigureTerminalSettings = 'workbench.action.terminal.openSettings', @@ -418,12 +418,12 @@ export const enum TerminalCommandId { NewInActiveWorkspace = 'workbench.action.terminal.newInActiveWorkspace', NewWithProfile = 'workbench.action.terminal.newWithProfile', Split = 'workbench.action.terminal.split', - SplitInstance = 'workbench.action.terminal.splitInstance', + SplitActiveTab = 'workbench.action.terminal.splitActiveTab', SplitInActiveWorkspace = 'workbench.action.terminal.splitInActiveWorkspace', ShowQuickFixes = 'workbench.action.terminal.showQuickFixes', Unsplit = 'workbench.action.terminal.unsplit', - UnsplitInstance = 'workbench.action.terminal.unsplitInstance', - JoinInstance = 'workbench.action.terminal.joinInstance', + UnsplitActiveTab = 'workbench.action.terminal.unsplitActiveTab', + JoinActiveTab = 'workbench.action.terminal.joinActiveTab', Join = 'workbench.action.terminal.join', Relaunch = 'workbench.action.terminal.relaunch', FocusPreviousPane = 'workbench.action.terminal.focusPreviousPane', @@ -436,7 +436,7 @@ export const enum TerminalCommandId { ResizePaneRight = 'workbench.action.terminal.resizePaneRight', ResizePaneUp = 'workbench.action.terminal.resizePaneUp', SizeToContentWidth = 'workbench.action.terminal.sizeToContentWidth', - SizeToContentWidthInstance = 'workbench.action.terminal.sizeToContentWidthInstance', + SizeToContentWidthActiveTab = 'workbench.action.terminal.sizeToContentWidthActiveTab', ResizePaneDown = 'workbench.action.terminal.resizePaneDown', Focus = 'workbench.action.terminal.focus', FocusNext = 'workbench.action.terminal.focusNext', @@ -457,13 +457,13 @@ export const enum TerminalCommandId { ClearSelection = 'workbench.action.terminal.clearSelection', ChangeIcon = 'workbench.action.terminal.changeIcon', ChangeIconPanel = 'workbench.action.terminal.changeIconPanel', - ChangeIconInstance = 'workbench.action.terminal.changeIconInstance', + ChangeIconActiveTab = 'workbench.action.terminal.changeIconActiveTab', ChangeColor = 'workbench.action.terminal.changeColor', ChangeColorPanel = 'workbench.action.terminal.changeColorPanel', - ChangeColorInstance = 'workbench.action.terminal.changeColorInstance', + ChangeColorActiveTab = 'workbench.action.terminal.changeColorActiveTab', Rename = 'workbench.action.terminal.rename', RenamePanel = 'workbench.action.terminal.renamePanel', - RenameInstance = 'workbench.action.terminal.renameInstance', + RenameActiveTab = 'workbench.action.terminal.renameActiveTab', RenameWithArgs = 'workbench.action.terminal.renameWithArg', FindFocus = 'workbench.action.terminal.focusFind', FindHide = 'workbench.action.terminal.hideFind', @@ -482,7 +482,7 @@ export const enum TerminalCommandId { AttachToSession = 'workbench.action.terminal.attachToSession', DetachSession = 'workbench.action.terminal.detachSession', MoveToEditor = 'workbench.action.terminal.moveToEditor', - MoveToEditorInstance = 'workbench.action.terminal.moveToEditorInstance', + MoveToEditorActiveTab = 'workbench.action.terminal.moveToEditorActiveTab', MoveToTerminalPanel = 'workbench.action.terminal.moveToTerminalPanel', SetDimensions = 'workbench.action.terminal.setDimensions', ClearPreviousSessionHistory = 'workbench.action.terminal.clearPreviousSessionHistory', diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 2ce2a66aa15..a6f9d4cb606 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -156,6 +156,12 @@ const apiMenus: IAPIMenu[] = [ description: localize('menus.terminalContext', "The terminal context menu"), proposed: 'terminalContextMenu' }, + { + key: 'terminal/title/context', + id: MenuId.TerminalTabContext, + description: localize('menus.terminalTabContext', "The terminal tabs context menu"), + proposed: 'terminalContextMenu' + }, { key: 'view/title', id: MenuId.ViewTitle, From b9d174e67d1f0163d1c61d5a368d9ee3b7c3a671 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 9 Aug 2023 12:09:20 -0700 Subject: [PATCH 54/69] Don't uninstall application-scoped extensions when importing profile (#190067) Don't uninstall application-scoped extensions --- .../services/userDataProfile/browser/extensionsResource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts b/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts index 3017e11df0e..5b228f19992 100644 --- a/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts +++ b/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts @@ -130,7 +130,7 @@ export class ExtensionsResource implements IProfileResource { extensionsToEnableOrDisable.push({ extension: e.identifier, enable: !e.disabled }); } } - const extensionsToUninstall: ILocalExtension[] = installedExtensions.filter(extension => !extension.isBuiltin && !profileExtensions.some(({ identifier }) => areSameExtensions(identifier, extension.identifier))); + const extensionsToUninstall: ILocalExtension[] = installedExtensions.filter(extension => !extension.isBuiltin && !profileExtensions.some(({ identifier }) => areSameExtensions(identifier, extension.identifier)) && !extension.isApplicationScoped); for (const { extension, enable } of extensionsToEnableOrDisable) { if (enable) { this.logService.trace(`Importing Profile (${profile.name}): Enabling extension...`, extension.id); From 1094b5d0655cf8cbd1b44a3c6cd97d029d96cc13 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:12:07 -0700 Subject: [PATCH 55/69] Reverting #189955 to fix issue with selectors in certain themes and adding specificity (#190077) * bugfix on selected list item reverting and added important * removed !important and added specificity --- src/vs/base/browser/ui/list/listWidget.ts | 2 +- src/vs/platform/actionWidget/browser/actionWidget.css | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index c02a2a53afe..36c5a74a948 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -830,7 +830,7 @@ export class DefaultStyleController implements IStyleController { } if (styles.listFocusForeground) { - content.push(`.monaco-list${suffix}:focus .monaco-list-row.focused:not(.monaco-list-row.action.option-disabled) { color: ${styles.listFocusForeground}; }`); + content.push(`.monaco-list${suffix}:focus .monaco-list-row.focused { color: ${styles.listFocusForeground}; }`); } if (styles.listActiveSelectionBackground) { diff --git a/src/vs/platform/actionWidget/browser/actionWidget.css b/src/vs/platform/actionWidget/browser/actionWidget.css index 7308524f84e..f76c030d9f2 100644 --- a/src/vs/platform/actionWidget/browser/actionWidget.css +++ b/src/vs/platform/actionWidget/browser/actionWidget.css @@ -93,10 +93,12 @@ } .action-widget .monaco-list-row.action.option-disabled, +.action-widget .monaco-list:focus .monaco-list-row.focused.action.option-disabled, .action-widget .monaco-list-row.action.option-disabled .codicon { color: var(--vscode-disabledForeground); } + .action-widget .monaco-list-row.action:not(.option-disabled) .codicon { color: inherit; } From a0e56dcea7f6bb2ebed09ec905f6c8f5d73d2049 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 9 Aug 2023 12:24:56 -0700 Subject: [PATCH 56/69] forwarding: fix tearing down process too early (#190081) --- extensions/tunnel-forwarding/src/extension.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/extensions/tunnel-forwarding/src/extension.ts b/extensions/tunnel-forwarding/src/extension.ts index f35d807ebe5..3dd6fdfef19 100644 --- a/extensions/tunnel-forwarding/src/extension.ts +++ b/extensions/tunnel-forwarding/src/extension.ts @@ -276,7 +276,11 @@ class TunnelProvider implements vscode.TunnelProvider { try { const l: { port_format: string } = JSON.parse(line); if (l.port_format && l.port_format !== lastPortFormat) { - this.state = { state: State.Active, portFormat: l.port_format, process }; + this.state = { + state: State.Active, + portFormat: l.port_format, process, + cleanupTimeout: 'cleanupTimeout' in this.state ? this.state.cleanupTimeout : undefined, + }; progressP.complete(); } } catch (e) { From 01f748f60792a8c3bb3b9863cab2771a9c8efaef Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 9 Aug 2023 12:36:15 -0700 Subject: [PATCH 57/69] Realign slash command content widget in panel chat (#190053) * Realign slash command content widget in panel chat * Align pill text with input text * Vertically align all content in panel chat input --------- Co-authored-by: David Dossett --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 2 +- .../contrib/chat/browser/chatSlashCommandContentWidget.css | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index bab2f25062d..d326a7ff85a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -197,7 +197,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge options.fontFamily = DEFAULT_FONT_FAMILY; options.fontSize = 13; options.lineHeight = 20; - options.padding = { top: 8, bottom: 7 }; + options.padding = { top: 8, bottom: 8 }; options.cursorWidth = 1; options.wrappingStrategy = 'advanced'; options.bracketPairColorization = { enabled: false }; diff --git a/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.css b/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.css index 4e9f2929009..441c10b0c43 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.css +++ b/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.css @@ -4,12 +4,8 @@ *--------------------------------------------------------------------------------------------*/ .chat-slash-command-content-widget { - padding: 0 3px 1px 3px; + padding: 1px 3px 1px 3px; border-radius: 3px; background-color: var(--vscode-chat-slashCommandBackground); color: var(--vscode-chat-slashCommandForeground); } - -.interactive-session .interactive-input-and-toolbar .chat-slash-command-content-widget { - top: 7px !important; -} From 8bd8248a76fffc0ec83d87b6842700f7d2a2bbc9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:47:03 -0700 Subject: [PATCH 58/69] Fix compilation --- src/vs/workbench/test/browser/workbenchTestServices.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 25804680d05..52a5c1aa21d 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1840,6 +1840,7 @@ export class TestTerminalGroupService implements ITerminalGroupService { groups: readonly ITerminalGroup[] = []; activeGroup: ITerminalGroup | undefined; activeGroupIndex: number = 0; + lastAccessedMenu: 'inline-tab' | 'tab-list' = 'inline-tab'; onDidChangeActiveGroup = Event.None; onDidDisposeGroup = Event.None; onDidShow = Event.None; From 3ee5c838efd2c94b7842e561a9ab017dbddccea2 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 9 Aug 2023 12:58:27 -0700 Subject: [PATCH 59/69] Update 1DS and add back envelope (#190056) * Update 1DS and add back envelope * Update missed 1DS packages --- package.json | 4 +- remote/package.json | 4 +- remote/web/package.json | 4 +- remote/web/yarn.lock | 78 ++++++++++++------- remote/yarn.lock | 78 ++++++++++++------- .../platform/telemetry/common/1dsAppender.ts | 16 ++-- yarn.lock | 78 ++++++++++++------- 7 files changed, 164 insertions(+), 98 deletions(-) diff --git a/package.json b/package.json index b6f40a6fd9d..a03624be57f 100644 --- a/package.json +++ b/package.json @@ -65,8 +65,8 @@ "perf": "node scripts/code-perf.js" }, "dependencies": { - "@microsoft/1ds-core-js": "^3.2.2", - "@microsoft/1ds-post-js": "^3.2.2", + "@microsoft/1ds-core-js": "^4.0.1", + "@microsoft/1ds-post-js": "^4.0.1", "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.1.4", diff --git a/remote/package.json b/remote/package.json index f68dbf03d29..b2163869961 100644 --- a/remote/package.json +++ b/remote/package.json @@ -3,8 +3,8 @@ "version": "0.0.0", "private": true, "dependencies": { - "@microsoft/1ds-core-js": "^3.2.2", - "@microsoft/1ds-post-js": "^3.2.2", + "@microsoft/1ds-core-js": "^4.0.1", + "@microsoft/1ds-post-js": "^4.0.1", "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/proxy-agent": "^0.17.2", diff --git a/remote/web/package.json b/remote/web/package.json index a0ab2c68bd2..881a5c41a33 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -3,8 +3,8 @@ "version": "0.0.0", "private": true, "dependencies": { - "@microsoft/1ds-core-js": "^3.2.2", - "@microsoft/1ds-post-js": "^3.2.2", + "@microsoft/1ds-core-js": "^4.0.1", + "@microsoft/1ds-post-js": "^4.0.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/vscode-languagedetection": "1.0.21", "jschardet": "3.0.0", diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index a6e62ad0a8f..01d3c44b85a 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -2,41 +2,63 @@ # yarn lockfile v1 -"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.2": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" - integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== +"@microsoft/1ds-core-js@4.0.1", "@microsoft/1ds-core-js@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-4.0.1.tgz#6fcf129276c933934d618f7fc3de49613259e3ec" + integrity sha512-BcTKhcRixCqLYEo1xVlQofvvZpgey9qmkzxQd1j9bBZS4iQl9IOmGBzlf/vGSITC7VXtEk2NZOlHQN0llAIfSw== dependencies: - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@microsoft/applicationinsights-core-js" "3.0.2" + "@microsoft/applicationinsights-shims" "^3.0.1" + "@microsoft/dynamicproto-js" "^2.0.2" + "@nevware21/ts-async" ">= 0.2.4 < 2.x" + "@nevware21/ts-utils" ">= 0.9.6 < 2.x" -"@microsoft/1ds-post-js@^3.2.2": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" - integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== +"@microsoft/1ds-post-js@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-4.0.1.tgz#8630927d10518067686104baec98557b74317e4e" + integrity sha512-uyYkG9mxzzj/4HgxVJsgcz9abfakW+Rjrsnor/hXS5I5ezE3YUX+54DLvAh/c6mEgoDI7u8wGkGl3HgCtAYbDQ== dependencies: - "@microsoft/1ds-core-js" "3.2.3" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@microsoft/1ds-core-js" "4.0.1" + "@microsoft/applicationinsights-shims" "^3.0.1" + "@microsoft/dynamicproto-js" "^2.0.2" + "@nevware21/ts-async" ">= 0.2.4 < 2.x" + "@nevware21/ts-utils" ">= 0.9.6 < 2.x" -"@microsoft/applicationinsights-core-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" - integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== +"@microsoft/applicationinsights-core-js@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.2.tgz#108e20df8c162bec92b1f66f9de2530a25d9f51a" + integrity sha512-WQhVhzlRlLDrQzn3OShCW/pL3BW5WC57t0oywSknX3q7lMzI3jDg7Ihh0iuIcNTzGCTbDkuqr4d6IjEDWIMtJQ== dependencies: - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@microsoft/applicationinsights-shims" "3.0.1" + "@microsoft/dynamicproto-js" "^2.0.2" + "@nevware21/ts-async" ">= 0.2.4 < 2.x" + "@nevware21/ts-utils" ">= 0.9.5 < 2.x" -"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" - integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== +"@microsoft/applicationinsights-shims@3.0.1", "@microsoft/applicationinsights-shims@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz#3865b73ace8405b9c4618cc5c571f2fe3876f06f" + integrity sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg== + dependencies: + "@nevware21/ts-utils" ">= 0.9.4 < 2.x" -"@microsoft/dynamicproto-js@^1.1.6": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" - integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== +"@microsoft/dynamicproto-js@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz#e57fbec2e7067d48b7e8e1e1c1d354028ef718a6" + integrity sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg== + dependencies: + "@nevware21/ts-utils" ">= 0.9.4 < 2.x" + +"@nevware21/ts-async@>= 0.2.4 < 2.x": + version "0.2.6" + resolved "https://registry.yarnpkg.com/@nevware21/ts-async/-/ts-async-0.2.6.tgz#9fad1c4a56b01430b407d454817f68bb6ace92b0" + integrity sha512-NCUqEZSbsy7LVtKlUScd/eTst6djkWauLlzoIPVKCOxalEBdO8lrgNRIm4Xy68JNudNN5faqa2WA12X8m0BVhA== + dependencies: + "@nevware21/ts-utils" ">= 0.9.7 < 2.x" + +"@nevware21/ts-utils@>= 0.9.4 < 2.x", "@nevware21/ts-utils@>= 0.9.5 < 2.x", "@nevware21/ts-utils@>= 0.9.6 < 2.x", "@nevware21/ts-utils@>= 0.9.7 < 2.x": + version "0.9.8" + resolved "https://registry.yarnpkg.com/@nevware21/ts-utils/-/ts-utils-0.9.8.tgz#6db23211a5c7539d3b4362e273087effcfc7d2f8" + integrity sha512-kZ8s8hcn9jPVX/M7kSsBYrOGlHjqLahmxrG7QeKTk5paeVwfgKdvVCjj5Acb4UGb/ukU1G34U1Z3eb7bbVanyA== "@vscode/iconv-lite-umd@0.7.0": version "0.7.0" diff --git a/remote/yarn.lock b/remote/yarn.lock index 77c2456f04d..09f295d5e4a 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -2,41 +2,63 @@ # yarn lockfile v1 -"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.2": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" - integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== +"@microsoft/1ds-core-js@4.0.1", "@microsoft/1ds-core-js@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-4.0.1.tgz#6fcf129276c933934d618f7fc3de49613259e3ec" + integrity sha512-BcTKhcRixCqLYEo1xVlQofvvZpgey9qmkzxQd1j9bBZS4iQl9IOmGBzlf/vGSITC7VXtEk2NZOlHQN0llAIfSw== dependencies: - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@microsoft/applicationinsights-core-js" "3.0.2" + "@microsoft/applicationinsights-shims" "^3.0.1" + "@microsoft/dynamicproto-js" "^2.0.2" + "@nevware21/ts-async" ">= 0.2.4 < 2.x" + "@nevware21/ts-utils" ">= 0.9.6 < 2.x" -"@microsoft/1ds-post-js@^3.2.2": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" - integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== +"@microsoft/1ds-post-js@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-4.0.1.tgz#8630927d10518067686104baec98557b74317e4e" + integrity sha512-uyYkG9mxzzj/4HgxVJsgcz9abfakW+Rjrsnor/hXS5I5ezE3YUX+54DLvAh/c6mEgoDI7u8wGkGl3HgCtAYbDQ== dependencies: - "@microsoft/1ds-core-js" "3.2.3" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@microsoft/1ds-core-js" "4.0.1" + "@microsoft/applicationinsights-shims" "^3.0.1" + "@microsoft/dynamicproto-js" "^2.0.2" + "@nevware21/ts-async" ">= 0.2.4 < 2.x" + "@nevware21/ts-utils" ">= 0.9.6 < 2.x" -"@microsoft/applicationinsights-core-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" - integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== +"@microsoft/applicationinsights-core-js@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.2.tgz#108e20df8c162bec92b1f66f9de2530a25d9f51a" + integrity sha512-WQhVhzlRlLDrQzn3OShCW/pL3BW5WC57t0oywSknX3q7lMzI3jDg7Ihh0iuIcNTzGCTbDkuqr4d6IjEDWIMtJQ== dependencies: - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@microsoft/applicationinsights-shims" "3.0.1" + "@microsoft/dynamicproto-js" "^2.0.2" + "@nevware21/ts-async" ">= 0.2.4 < 2.x" + "@nevware21/ts-utils" ">= 0.9.5 < 2.x" -"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" - integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== +"@microsoft/applicationinsights-shims@3.0.1", "@microsoft/applicationinsights-shims@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz#3865b73ace8405b9c4618cc5c571f2fe3876f06f" + integrity sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg== + dependencies: + "@nevware21/ts-utils" ">= 0.9.4 < 2.x" -"@microsoft/dynamicproto-js@^1.1.6": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" - integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== +"@microsoft/dynamicproto-js@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz#e57fbec2e7067d48b7e8e1e1c1d354028ef718a6" + integrity sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg== + dependencies: + "@nevware21/ts-utils" ">= 0.9.4 < 2.x" + +"@nevware21/ts-async@>= 0.2.4 < 2.x": + version "0.2.6" + resolved "https://registry.yarnpkg.com/@nevware21/ts-async/-/ts-async-0.2.6.tgz#9fad1c4a56b01430b407d454817f68bb6ace92b0" + integrity sha512-NCUqEZSbsy7LVtKlUScd/eTst6djkWauLlzoIPVKCOxalEBdO8lrgNRIm4Xy68JNudNN5faqa2WA12X8m0BVhA== + dependencies: + "@nevware21/ts-utils" ">= 0.9.7 < 2.x" + +"@nevware21/ts-utils@>= 0.9.4 < 2.x", "@nevware21/ts-utils@>= 0.9.5 < 2.x", "@nevware21/ts-utils@>= 0.9.6 < 2.x", "@nevware21/ts-utils@>= 0.9.7 < 2.x": + version "0.9.8" + resolved "https://registry.yarnpkg.com/@nevware21/ts-utils/-/ts-utils-0.9.8.tgz#6db23211a5c7539d3b4362e273087effcfc7d2f8" + integrity sha512-kZ8s8hcn9jPVX/M7kSsBYrOGlHjqLahmxrG7QeKTk5paeVwfgKdvVCjj5Acb4UGb/ukU1G34U1Z3eb7bbVanyA== "@parcel/watcher@2.1.0": version "2.1.0" diff --git a/src/vs/platform/telemetry/common/1dsAppender.ts b/src/vs/platform/telemetry/common/1dsAppender.ts index 02295cc4968..ccfbc3f7d9b 100644 --- a/src/vs/platform/telemetry/common/1dsAppender.ts +++ b/src/vs/platform/telemetry/common/1dsAppender.ts @@ -52,14 +52,14 @@ async function getClient(instrumentationKey: string, addInternalFlag?: boolean, appInsightsCore.initialize(coreConfig, []); - // appInsightsCore.addTelemetryInitializer((envelope) => { - // if (addInternalFlag) { - // envelope['ext'] = envelope['ext'] ?? {}; - // envelope['ext']['utc'] = envelope['ext']['utc'] ?? {}; - // // Sets it to be internal only based on Windows UTC flagging - // envelope['ext']['utc']['flags'] = 0x0000811ECD; - // } - // }); + appInsightsCore.addTelemetryInitializer((envelope) => { + if (addInternalFlag) { + envelope['ext'] = envelope['ext'] ?? {}; + envelope['ext']['utc'] = envelope['ext']['utc'] ?? {}; + // Sets it to be internal only based on Windows UTC flagging + envelope['ext']['utc']['flags'] = 0x0000811ECD; + } + }); return appInsightsCore; } diff --git a/yarn.lock b/yarn.lock index 451a8cbd0b6..010195c564d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -474,41 +474,51 @@ methods "^1.1.2" path-to-regexp "^6.2.1" -"@microsoft/1ds-core-js@3.2.2", "@microsoft/1ds-core-js@^3.2.2": - version "3.2.2" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.2.tgz#86c5afb3c6357f5d96c12086e9fe46fd2ebd82e7" - integrity sha512-YUsH2WAIBjM8d3weiwuvkhTZxBIlQs8ssPz5pSsVEzHXfqPU5Z66ZjWu3Wmmp4sF+8rk6oZel4EBEusf7xMTYw== +"@microsoft/1ds-core-js@4.0.1", "@microsoft/1ds-core-js@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-4.0.1.tgz#6fcf129276c933934d618f7fc3de49613259e3ec" + integrity sha512-BcTKhcRixCqLYEo1xVlQofvvZpgey9qmkzxQd1j9bBZS4iQl9IOmGBzlf/vGSITC7VXtEk2NZOlHQN0llAIfSw== dependencies: - "@microsoft/applicationinsights-core-js" "2.8.3" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@microsoft/applicationinsights-core-js" "3.0.2" + "@microsoft/applicationinsights-shims" "^3.0.1" + "@microsoft/dynamicproto-js" "^2.0.2" + "@nevware21/ts-async" ">= 0.2.4 < 2.x" + "@nevware21/ts-utils" ">= 0.9.6 < 2.x" -"@microsoft/1ds-post-js@^3.2.2": - version "3.2.2" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.2.tgz#cbe5f8c1d1281c7177448ca3a48f16d4d4bdeed7" - integrity sha512-PXs+1XfA2lRNgLQq8w3x786hPLTnT6m599ROUCjgtg5wU7xOdfXFLpVw+5Okd9JD81ZhY4xXXuvVvtGu1/j1sg== +"@microsoft/1ds-post-js@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-4.0.1.tgz#8630927d10518067686104baec98557b74317e4e" + integrity sha512-uyYkG9mxzzj/4HgxVJsgcz9abfakW+Rjrsnor/hXS5I5ezE3YUX+54DLvAh/c6mEgoDI7u8wGkGl3HgCtAYbDQ== dependencies: - "@microsoft/1ds-core-js" "3.2.2" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@microsoft/1ds-core-js" "4.0.1" + "@microsoft/applicationinsights-shims" "^3.0.1" + "@microsoft/dynamicproto-js" "^2.0.2" + "@nevware21/ts-async" ">= 0.2.4 < 2.x" + "@nevware21/ts-utils" ">= 0.9.6 < 2.x" -"@microsoft/applicationinsights-core-js@2.8.3": - version "2.8.3" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.3.tgz#d07abd6e8bfec7d101518494ad4bd62516df5c51" - integrity sha512-gVx8G3P4a7XmyCPPCL1nhlXesNktwEQ4XLWQ+DG1F0pEe7akpywCGPrAwswPSiK3mrAKgowqB2nxYYLvXhp0PA== +"@microsoft/applicationinsights-core-js@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.2.tgz#108e20df8c162bec92b1f66f9de2530a25d9f51a" + integrity sha512-WQhVhzlRlLDrQzn3OShCW/pL3BW5WC57t0oywSknX3q7lMzI3jDg7Ihh0iuIcNTzGCTbDkuqr4d6IjEDWIMtJQ== dependencies: - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@microsoft/applicationinsights-shims" "3.0.1" + "@microsoft/dynamicproto-js" "^2.0.2" + "@nevware21/ts-async" ">= 0.2.4 < 2.x" + "@nevware21/ts-utils" ">= 0.9.5 < 2.x" -"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" - integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== +"@microsoft/applicationinsights-shims@3.0.1", "@microsoft/applicationinsights-shims@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz#3865b73ace8405b9c4618cc5c571f2fe3876f06f" + integrity sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg== + dependencies: + "@nevware21/ts-utils" ">= 0.9.4 < 2.x" -"@microsoft/dynamicproto-js@^1.1.6": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" - integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== +"@microsoft/dynamicproto-js@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz#e57fbec2e7067d48b7e8e1e1c1d354028ef718a6" + integrity sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg== + dependencies: + "@nevware21/ts-utils" ">= 0.9.4 < 2.x" "@mole-inc/bin-wrapper@^8.0.1": version "8.0.1" @@ -524,6 +534,18 @@ got "^11.8.5" os-filter-obj "^2.0.0" +"@nevware21/ts-async@>= 0.2.4 < 2.x": + version "0.2.6" + resolved "https://registry.yarnpkg.com/@nevware21/ts-async/-/ts-async-0.2.6.tgz#9fad1c4a56b01430b407d454817f68bb6ace92b0" + integrity sha512-NCUqEZSbsy7LVtKlUScd/eTst6djkWauLlzoIPVKCOxalEBdO8lrgNRIm4Xy68JNudNN5faqa2WA12X8m0BVhA== + dependencies: + "@nevware21/ts-utils" ">= 0.9.7 < 2.x" + +"@nevware21/ts-utils@>= 0.9.4 < 2.x", "@nevware21/ts-utils@>= 0.9.5 < 2.x", "@nevware21/ts-utils@>= 0.9.6 < 2.x", "@nevware21/ts-utils@>= 0.9.7 < 2.x": + version "0.9.8" + resolved "https://registry.yarnpkg.com/@nevware21/ts-utils/-/ts-utils-0.9.8.tgz#6db23211a5c7539d3b4362e273087effcfc7d2f8" + integrity sha512-kZ8s8hcn9jPVX/M7kSsBYrOGlHjqLahmxrG7QeKTk5paeVwfgKdvVCjj5Acb4UGb/ukU1G34U1Z3eb7bbVanyA== + "@nodelib/fs.scandir@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" From 432aac18f1c2929872ebd475a77145195a22c965 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 9 Aug 2023 23:29:30 +0300 Subject: [PATCH 60/69] [typescript] Better paths matching for move to existing file quickpick (#181231) * Better paths matching for move to existing file quickpick * fix resolving selected item * fix: select file dialog point to existing file * skip computing if use coalesce * Improve typings --------- Co-authored-by: Matt Bierner --- .../src/languageFeatures/refactor.ts | 103 ++++++++++++------ 1 file changed, 68 insertions(+), 35 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/refactor.ts b/extensions/typescript-language-features/src/languageFeatures/refactor.ts index a99b92685fc..ae36fbffb22 100644 --- a/extensions/typescript-language-features/src/languageFeatures/refactor.ts +++ b/extensions/typescript-language-features/src/languageFeatures/refactor.ts @@ -175,47 +175,80 @@ class MoveToFileRefactorCommand implements Command { if (response.type !== 'response' || !response.body) { return; } + const body = response.body; - const selectExistingFileItem: vscode.QuickPickItem = { - label: vscode.l10n.t("Select existing file..."), - }; - const selectNewFileItem: vscode.QuickPickItem = { - label: vscode.l10n.t("Enter new file path..."), - }; - - type DestinationItem = vscode.QuickPickItem & { readonly file: string }; + type DestinationItem = vscode.QuickPickItem & { readonly file?: string }; + const selectExistingFileItem: vscode.QuickPickItem = { label: vscode.l10n.t("Select existing file...") }; + const selectNewFileItem: vscode.QuickPickItem = { label: vscode.l10n.t("Enter new file path...") }; const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri); - const destinationItems = response.body.files.map((file): DestinationItem => { - const uri = this.client.toResource(file); - const parentDir = Utils.dirname(uri); + const quickPick = vscode.window.createQuickPick(); + quickPick.ignoreFocusOut = true; - let description; - if (workspaceFolder) { - if (uri.scheme === Schemes.file) { - description = path.relative(workspaceFolder.uri.fsPath, parentDir.fsPath); - } else { - description = path.posix.relative(workspaceFolder.uri.path, parentDir.path); - } - } else { - description = parentDir.fsPath; + // true so we don't skip computing in the first call + let quickPickInRelativeMode = true; + const updateItems = () => { + const relativeQuery = ['./', '../'].find(str => quickPick.value.startsWith(str)); + if (quickPickInRelativeMode === false && !!relativeQuery === false) { + return; } + quickPickInRelativeMode = !!relativeQuery; + const destinationItems = body.files.map((file): DestinationItem | undefined => { + const uri = this.client.toResource(file); + const parentDir = Utils.dirname(uri); + const filename = Utils.basename(uri); - return { - file, - label: Utils.basename(uri), - description, - }; - }); + let description: string | undefined; + if (workspaceFolder) { + if (uri.scheme === Schemes.file) { + description = path.relative(workspaceFolder.uri.fsPath, parentDir.fsPath); + } else { + description = path.posix.relative(workspaceFolder.uri.path, parentDir.path); + } + if (relativeQuery) { + const convertRelativePath = (str: string) => { + return !str.startsWith('../') ? `./${str}` : str; + }; - const picked = await vscode.window.showQuickPick([ - selectExistingFileItem, - selectNewFileItem, - { label: vscode.l10n.t("Destination Files"), kind: vscode.QuickPickItemKind.Separator }, - ...destinationItems - ], { - title: vscode.l10n.t("Move to File"), - placeHolder: vscode.l10n.t("Select move destination"), + const relativePath = convertRelativePath(path.relative(path.dirname(document.uri.fsPath), uri.fsPath)); + if (!relativePath.startsWith(relativeQuery)) { + return; + } + description = relativePath; + } + } else { + description = parentDir.fsPath; + } + + return { + file, + label: Utils.basename(uri), + description: relativeQuery ? description : path.join(description, filename), + }; + }); + quickPick.items = [ + selectExistingFileItem, + selectNewFileItem, + { label: vscode.l10n.t("Destination Files"), kind: vscode.QuickPickItemKind.Separator }, + ...coalesce(destinationItems) + ]; + }; + quickPick.title = vscode.l10n.t("Move to File"); + quickPick.placeholder = vscode.l10n.t("Enter file path"); + quickPick.matchOnDescription = true; + quickPick.onDidChangeValue(updateItems); + updateItems(); + + const picked = await new Promise(resolve => { + quickPick.onDidAccept(() => { + resolve(quickPick.selectedItems[0]); + quickPick.dispose(); + }); + quickPick.onDidHide(() => { + resolve(undefined); + quickPick.dispose(); + }); + quickPick.show(); }); if (!picked) { return; @@ -236,7 +269,7 @@ class MoveToFileRefactorCommand implements Command { }); return picked ? this.client.toTsFilePath(picked) : undefined; } else { - return (picked as DestinationItem).file; + return picked.file; } } } From 2116696377bf620f9e93bc047ba14351dfdcc3a9 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Aug 2023 13:50:17 -0700 Subject: [PATCH 61/69] fix issues --- .../browser/accessibilityContributions.ts | 3 +- .../accessibility/browser/accessibleView.ts | 184 +----------------- .../browser/accessibleViewActions.ts | 19 -- .../chat/browser/actions/chatActions.ts | 2 +- .../contrib/chat/browser/chat.contribution.ts | 3 +- .../codeEditor/browser/diffEditorHelper.ts | 3 +- .../browser/inlineChat.contribution.ts | 3 +- .../inlineChat/browser/inlineChatActions.ts | 2 +- .../notebook/browser/notebook.contribution.ts | 3 +- .../terminal.accessibility.contribution.ts | 3 +- 10 files changed, 21 insertions(+), 204 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts index 0fe95024b71..16b2065c405 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts @@ -27,9 +27,10 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { getNotificationFromContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { IListService, WorkbenchList } from 'vs/platform/list/browser/listService'; import { NotificationFocusedContext } from 'vs/workbench/common/contextkeys'; -import { IAccessibleViewService, IAccessibleContentProvider, IAccessibleViewOptions, AccessibleViewType, AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { IAccessibleViewService, IAccessibleContentProvider, IAccessibleViewOptions, AccessibleViewType } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; import { alert } from 'vs/base/browser/ui/aria/aria'; +import { AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; export class EditorAccessibilityHelpContribution extends Disposable { static ID: 'editorAccessibilityHelpContribution'; diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 05643b5c838..8b9d4ef3d13 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -6,14 +6,13 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { alert } from 'vs/base/browser/ui/aria/aria'; -import { Codicon } from 'vs/base/common/codicons'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { marked } from 'vs/base/common/marked/marked'; import { isMacintosh } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; -import { Command, EditorExtensionsRegistry, MultiCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; @@ -22,18 +21,18 @@ import { CodeActionController } from 'vs/editor/contrib/codeAction/browser/codeA import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; -import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewDelegate, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { AccessibilityVerbositySettingId, accessibilityHelpIsShown, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; const enum DIMENSIONS { @@ -388,7 +387,8 @@ class AccessibleView extends Disposable { private _getAccessibleViewHelpDialogContent(providerHasSymbols?: boolean): string { const navigationHint = this._getNavigationHint(); const goToSymbolHint = this._getGoToSymbolHint(providerHasSymbols); - const toolbarHint = this._getToolbarHint(); + const toolbarHint = localize('toolbar', "Navigate to the toolbar ({0} or Shift+Tab)"); + let hint = localize('intro', "In the accessible view, you can:\n"); if (navigationHint) { hint += ' - ' + navigationHint + '\n'; @@ -402,17 +402,6 @@ class AccessibleView extends Disposable { return hint; } - private _getToolbarHint(): string { - let toolbarHint = ''; - const toolbarKb = this._keybindingService.lookupKeybinding('editor.action.accessibleViewFocusToolbar')?.getLabel(); - if (toolbarKb) { - toolbarHint = localize('toolbar', "Navigate to the toolbar ({0} or Shift+Tab)", toolbarKb); - } else { - toolbarHint = localize('toolbarNoKb', "Navigate to the toolbar (Shift+Tab)"); - } - return toolbarHint; - } - private _getNavigationHint(): string { let hint = ''; const nextKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleViewNext')?.getAriaLabel(); @@ -543,162 +532,3 @@ interface IAccessibleViewSymbol extends IPickerQuickAccessItem { info: string; firstListItem?: string; } -const accessibleViewMenu = { - id: MenuId.AccessibleView, - group: 'navigation', - when: accessibleViewIsShown -}; -const commandPalette = { - id: MenuId.CommandPalette, - group: '', - order: 1 -}; -class AccessibleViewNextAction extends Action2 { - constructor() { - super({ - id: 'editor.action.accessibleViewNext', - precondition: accessibleViewIsShown, - keybinding: { - primary: KeyMod.Alt | KeyCode.BracketRight, - weight: KeybindingWeight.WorkbenchContrib - }, - menu: [commandPalette, accessibleViewMenu], - icon: Codicon.chevronRight, - title: localize('editor.action.accessibleViewNext', "Show Next in Accessible View") - }); - } - run(accessor: ServicesAccessor): void { - accessor.get(IAccessibleViewService).next(); - } -} -registerAction2(AccessibleViewNextAction); - - -class AccessibleViewPreviousAction extends Action2 { - constructor() { - super({ - id: 'editor.action.accessibleViewPrevious', - precondition: accessibleViewIsShown, - keybinding: { - primary: KeyMod.Alt | KeyCode.BracketLeft, - weight: KeybindingWeight.WorkbenchContrib - }, - icon: Codicon.chevronLeft, - menu: [commandPalette, accessibleViewMenu], - title: localize('editor.action.accessibleViewPrevious', "Show Previous in Accessible View") - }); - } - run(accessor: ServicesAccessor): void { - accessor.get(IAccessibleViewService).previous(); - } -} -registerAction2(AccessibleViewPreviousAction); - - -class AccessibleViewGoToSymbolAction extends Action2 { - constructor() { - super({ - id: 'editor.action.accessibleViewGoToSymbol', - precondition: accessibleViewIsShown, - keybinding: { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO, - weight: KeybindingWeight.WorkbenchContrib + 10 - }, - icon: Codicon.symbolField, - menu: [commandPalette, accessibleViewMenu], - title: localize('editor.action.accessibleViewGoToSymbol', "Go To Symbol in Accessible View") - }); - } - run(accessor: ServicesAccessor): void { - accessor.get(IAccessibleViewService).goToSymbol(); - } -} -registerAction2(AccessibleViewGoToSymbolAction); - -function registerCommand(command: T): T { - command.register(); - return command; -} - -export const AccessibilityHelpAction = registerCommand(new MultiCommand({ - id: 'editor.action.accessibilityHelp', - precondition: undefined, - kbOpts: { - primary: KeyMod.Alt | KeyCode.F1, - weight: KeybindingWeight.WorkbenchContrib, - linux: { - primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F1, - secondary: [KeyMod.Alt | KeyCode.F1] - } - }, - menuOpts: [{ - menuId: MenuId.CommandPalette, - group: '', - title: localize('editor.action.accessibilityHelp', "Open Accessibility Help"), - order: 1 - }], -})); - - -export const AccessibleViewAction = registerCommand(new MultiCommand({ - id: 'editor.action.accessibleView', - precondition: undefined, - kbOpts: { - primary: KeyMod.Alt | KeyCode.F2, - weight: KeybindingWeight.WorkbenchContrib, - linux: { - primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F2, - secondary: [KeyMod.Alt | KeyCode.F2] - } - }, - menuOpts: [{ - menuId: MenuId.CommandPalette, - group: '', - title: localize('editor.action.accessibleView', "Open Accessible View"), - order: 1 - }], -})); - -class AccessibleViewDisableHintAction extends Action2 { - constructor() { - super({ - id: 'editor.action.accessibleViewDisableHint', - keybinding: { - when: ContextKeyExpr.or(accessibleViewIsShown, accessibilityHelpIsShown), - primary: KeyMod.Alt | KeyCode.F6, - weight: KeybindingWeight.WorkbenchContrib - }, - icon: Codicon.treeFilterClear, - menu: [commandPalette, - { - id: MenuId.AccessibleView, - group: 'navigation', - when: ContextKeyExpr.or(accessibleViewIsShown, accessibilityHelpIsShown) - }], - title: localize('editor.action.accessibleViewDisableHint', "Disable Accessible View Hint") - }); - } - run(accessor: ServicesAccessor): void { - accessor.get(IAccessibleViewService).disableHint(); - } -} -registerAction2(AccessibleViewDisableHintAction); - -class AccessibleViewFocusToolbarAction extends Action2 { - constructor() { - super({ - id: 'editor.action.accessibleViewFocusToolbar', - keybinding: { - when: accessibleViewIsShown, - primary: KeyMod.Alt | KeyCode.F7, - weight: KeybindingWeight.WorkbenchContrib - }, - menu: [commandPalette], - title: localize('editor.action.accessibleViewFocusToolbar', "Accessible View Focus Toolbar") - }); - } - run(accessor: ServicesAccessor): void { - accessor.get(IAccessibleViewService).focusToolbar(); - } -} -registerAction2(AccessibleViewFocusToolbarAction); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts index e68fc303fcc..3d4a87e04dc 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts @@ -153,22 +153,3 @@ class AccessibleViewDisableHintAction extends Action2 { } } registerAction2(AccessibleViewDisableHintAction); - -class AccessibleViewFocusToolbarAction extends Action2 { - constructor() { - super({ - id: 'editor.action.accessibleViewFocusToolbar', - keybinding: { - when: accessibleViewIsShown, - primary: KeyMod.Alt | KeyCode.F7, - weight: KeybindingWeight.WorkbenchContrib - }, - menu: [commandPalette], - title: localize('editor.action.accessibleViewFocusToolbar', "Accessible View Focus Toolbar") - }); - } - run(accessor: ServicesAccessor): void { - accessor.get(IAccessibleViewService).focusToolbar(); - } -} -registerAction2(AccessibleViewFocusToolbarAction); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 36c78c52993..8b783da509f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -29,7 +29,7 @@ import { IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chat import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; export const CHAT_CATEGORY = { value: localize('chat.category', "Chat"), original: 'Chat' }; diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index c3bd91e88fb..1f366894d67 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -37,7 +37,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import '../common/chatColors'; import { registerMoveActions } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions'; import { registerClearActions } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; -import { AccessibleViewAction, AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService'; @@ -48,6 +48,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { ChatProviderService, IChatProviderService } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { alertFocusChange } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions'; +import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index d4fef318c62..26754add9a0 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -20,7 +20,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { AccessibilityHelpAction, AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; class DiffEditorHelperContribution extends Disposable implements IDiffEditorContribution { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index 5c72e361d0e..8b6c004f074 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -15,12 +15,13 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { InlineChatNotebookContribution } from 'vs/workbench/contrib/inlineChat/browser/inlineChatNotebook'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { AccessibleViewAction, AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { Disposable } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { localize } from 'vs/nls'; import { Extensions, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; registerSingleton(IInlineChatService, InlineChatServiceImpl, InstantiationType.Delayed); registerSingleton(IInlineChatSessionService, InlineChatSessionService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index eda55f31d9b..05bb019ec59 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -31,7 +31,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { Position } from 'vs/editor/common/core/position'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 7df3d82cc89..15ac6a3068e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -114,8 +114,9 @@ import { NotebookLoggingService } from 'vs/workbench/contrib/notebook/browser/se import product from 'vs/platform/product/common/product'; import { NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { runAccessibilityHelpAction, showAccessibleOutput } from 'vs/workbench/contrib/notebook/browser/notebookAccessibility'; -import { AccessibilityHelpAction, AccessibleViewAction, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; /*--------------------------------------------------------------------------------------------- */ 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 0af08a0985a..cc697917066 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 @@ -12,7 +12,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { terminalTabFocusModeContextKey } from 'vs/platform/terminal/common/terminal'; -import { AccessibilityHelpAction, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; From 34122ea014ef99f10e54908141bbb94bc54a1000 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 9 Aug 2023 14:15:20 -0700 Subject: [PATCH 62/69] Add more keywords to UnfocusedViewOpacity Part of #30522 --- .../contrib/accessibility/browser/accessibilityConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 24da4ec7ff3..061d074807a 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -71,7 +71,7 @@ const configuration: IConfigurationNode = { ...baseProperty }, [AccessibilitySettingId.UnfocusedViewOpacity]: { - description: localize('unfocusedViewOpacity', 'The opacity percentage (0.2 to 1.0) to use for unfocused editors and terminals.'), + description: localize('unfocusedViewOpacity', 'The opacity percentage (0.2 to 1.0) to use for unfocused editors and terminals. This allows dimming of inactive views to make it more obvious what is focused.'), type: 'number', minimum: 0.2, maximum: 1, From a3e2f154406d9e11768b436cf71c5fe3f8185125 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 9 Aug 2023 23:16:02 +0200 Subject: [PATCH 63/69] Add tunnel activation event (#189936) * Add tunnel activation event Fixes #188730 * Refactor --- .../api/browser/mainThreadTunnelService.ts | 3 +- .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostTunnelService.ts | 2 +- .../api/node/extHostTunnelService.ts | 2 +- src/vs/workbench/browser/web.main.ts | 3 +- .../contrib/remote/browser/remoteExplorer.ts | 3 +- .../contrib/remote/browser/showCandidate.ts | 3 +- .../contrib/remote/browser/tunnelView.ts | 3 +- .../remote/common/remoteExplorerService.ts | 935 +----------------- .../services/remote/common/tunnelModel.ts | 932 +++++++++++++++++ 10 files changed, 952 insertions(+), 936 deletions(-) create mode 100644 src/vs/workbench/services/remote/common/tunnelModel.ts diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index f2633afa045..939b20fee29 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { MainThreadTunnelServiceShape, MainContext, ExtHostContext, ExtHostTunnelServiceShape, CandidatePortSource, PortAttributesSelector, TunnelDto } from 'vs/workbench/api/common/extHost.protocol'; import { TunnelDtoConverter } from 'vs/workbench/api/common/extHostTunnelService'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { CandidatePort, IRemoteExplorerService, makeAddress, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, TunnelCloseReason, TunnelSource } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IRemoteExplorerService, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, ProvidedPortAttributes, PortAttributesProvider, TunnelProtocol } from 'vs/platform/tunnel/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver'; @@ -20,6 +20,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { forwardedPortsViewEnabled } from 'vs/workbench/contrib/remote/browser/tunnelView'; +import { CandidatePort, TunnelCloseReason, TunnelSource, makeAddress } from 'vs/workbench/services/remote/common/tunnelModel'; @extHostNamedCustomer(MainContext.MainThreadTunnelService) export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape, PortAttributesProvider { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 76565ab6f14..e1c30c60aa7 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -69,7 +69,7 @@ import { ActivationKind, ExtensionActivationReason, MissingExtensionDependency } import { Dto, IRPCProtocol, SerializableObjectWithBuffers, createProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { ILanguageStatus } from 'vs/workbench/services/languageStatus/common/languageStatusService'; import { OutputChannelUpdateMode } from 'vs/workbench/services/output/common/output'; -import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel'; import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; import * as search from 'vs/workbench/services/search/common/search'; import { ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index fbebe3ba9ed..677a37a84c5 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -15,7 +15,7 @@ import { ExtHostTunnelServiceShape, MainContext, MainThreadTunnelServiceShape, P import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import * as types from 'vs/workbench/api/common/extHostTypes'; -import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel'; import * as vscode from 'vscode'; class ExtensionTunnel extends DisposableTunnel implements vscode.Tunnel { } diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index ac5e6717293..56e0de8c855 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -14,7 +14,7 @@ import { isAllInterfaces, isLocalhost } from 'vs/platform/tunnel/common/tunnel'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; -import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel'; export function getSockets(stdout: string): Record { const lines = stdout.trim().split('\n'); diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 4cec41e3b9a..c14c64c48c9 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -73,7 +73,7 @@ import { DelayedLogChannel } from 'vs/workbench/services/output/common/delayedLo import { dirname, joinPath } from 'vs/base/common/resources'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { NullPolicyService } from 'vs/platform/policy/common/policy'; -import { IRemoteExplorerService, TunnelSource } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { DisposableTunnel, TunnelProtocol } from 'vs/platform/tunnel/common/tunnel'; import { ILabelService } from 'vs/platform/label/common/label'; import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; @@ -96,6 +96,7 @@ import { BrowserSecretStorageService } from 'vs/workbench/services/secrets/brows import { EncryptionService } from 'vs/workbench/services/encryption/browser/encryptionService'; import { IEncryptionService } from 'vs/platform/encryption/common/encryptionService'; import { ISecretStorageService } from 'vs/platform/secrets/common/secrets'; +import { TunnelSource } from 'vs/workbench/services/remote/common/tunnelModel'; export class BrowserMain extends Disposable { diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index 433c796498c..d43d5222c06 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -6,7 +6,8 @@ import * as nls from 'vs/nls'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Extensions, IViewContainersRegistry, IViewsRegistry, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; -import { Attributes, AutoTunnelSource, IRemoteExplorerService, makeAddress, mapHasAddressLocalhostOrAllInterfaces, OnPortForward, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_HYBRID, PORT_AUTO_SOURCE_SETTING_OUTPUT, PORT_AUTO_SOURCE_SETTING_PROCESS, Tunnel, TUNNEL_VIEW_CONTAINER_ID, TUNNEL_VIEW_ID, TunnelCloseReason, TunnelSource } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IRemoteExplorerService, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_HYBRID, PORT_AUTO_SOURCE_SETTING_OUTPUT, PORT_AUTO_SOURCE_SETTING_PROCESS, TUNNEL_VIEW_CONTAINER_ID, TUNNEL_VIEW_ID } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { Attributes, AutoTunnelSource, makeAddress, mapHasAddressLocalhostOrAllInterfaces, OnPortForward, Tunnel, TunnelCloseReason, TunnelSource } from 'vs/workbench/services/remote/common/tunnelModel'; import { forwardedPortsViewEnabled, ForwardPortAction, OpenPortInBrowserAction, TunnelPanel, TunnelPanelDescriptor, TunnelViewModel, OpenPortInPreviewAction, openPreviewEnabledContext } from 'vs/workbench/contrib/remote/browser/tunnelView'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; diff --git a/src/vs/workbench/contrib/remote/browser/showCandidate.ts b/src/vs/workbench/contrib/remote/browser/showCandidate.ts index 9736fc5fd10..47f2f690e33 100644 --- a/src/vs/workbench/contrib/remote/browser/showCandidate.ts +++ b/src/vs/workbench/contrib/remote/browser/showCandidate.ts @@ -6,7 +6,8 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; -import { CandidatePort, IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel'; export class ShowCandidateContribution extends Disposable implements IWorkbenchContribution { constructor( diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 353966c2488..0a65be4fbed 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -24,7 +24,7 @@ import { ActionRunner, IAction } from 'vs/base/common/actions'; import { IMenuService, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { createAndFillInActionBarActions, createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IRemoteExplorerService, TunnelModel, makeAddress, TunnelType, ITunnelItem, Tunnel, TUNNEL_VIEW_ID, parseAddress, CandidatePort, TunnelEditId, mapHasAddressLocalhostOrAllInterfaces, Attributes, TunnelSource, TunnelCloseReason } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IRemoteExplorerService, TunnelType, ITunnelItem, TUNNEL_VIEW_ID, TunnelEditId } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; @@ -55,6 +55,7 @@ import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; import { STATUS_BAR_HOST_NAME_BACKGROUND } from 'vs/workbench/common/theme'; import { Codicon } from 'vs/base/common/codicons'; import { defaultButtonStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { Attributes, CandidatePort, Tunnel, TunnelCloseReason, TunnelModel, TunnelSource, makeAddress, mapHasAddressLocalhostOrAllInterfaces, parseAddress } from 'vs/workbench/services/remote/common/tunnelModel'; export const forwardedPortsViewEnabled = new RawContextKey('forwardedPortsViewEnabled', false, nls.localize('tunnel.forwardedPortsViewEnabled', "Whether the Ports view is enabled.")); export const openPreviewEnabledContext = new RawContextKey('openPreviewEnabled', false); diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 4f0036cdf11..76162f95825 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -3,32 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { ALL_INTERFACES_ADDRESSES, isAllInterfaces, isLocalhost, ITunnelService, LOCALHOST_ADDRESSES, PortAttributesProvider, ProvidedOnAutoForward, ProvidedPortAttributes, RemoteTunnel, TunnelPrivacyId, TunnelProtocol } from 'vs/platform/tunnel/common/tunnel'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { ITunnelService, RemoteTunnel, TunnelProtocol } from 'vs/platform/tunnel/common/tunnel'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { IEditableData } from 'vs/workbench/common/views'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TunnelInformation, TunnelDescription, IRemoteAuthorityResolverService, TunnelPrivacy } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnection'; -import { isNumber, isObject, isString } from 'vs/base/common/types'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { hash } from 'vs/base/common/hash'; -import { ILogService } from 'vs/platform/log/common/log'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { flatten } from 'vs/base/common/arrays'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { TunnelInformation, TunnelPrivacy } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { URI } from 'vs/base/common/uri'; -import { deepClone } from 'vs/base/common/objects'; -import { debounce } from 'vs/base/common/decorators'; +import { Attributes, CandidatePort, TunnelCloseReason, TunnelModel, TunnelProperties, TunnelSource } from 'vs/workbench/services/remote/common/tunnelModel'; export const IRemoteExplorerService = createDecorator('remoteExplorerService'); export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType'; -const TUNNELS_TO_RESTORE = 'remote.tunnels.toRestore'; export const TUNNEL_VIEW_ID = '~remote.forwardedPorts'; export const TUNNEL_VIEW_CONTAINER_ID = '~remote.forwardedPortsContainer'; export const PORT_AUTO_FORWARD_SETTING = 'remote.autoForwardPorts'; @@ -44,12 +31,6 @@ export enum TunnelType { Add = 'Add' } -export enum TunnelCloseReason { - Other = 'Other', - User = 'User', - AutoForwardEnd = 'AutoForwardEnd', -} - export interface ITunnelItem { tunnelType: TunnelType; remoteHost: string; @@ -76,902 +57,6 @@ export enum TunnelEditId { LocalPort = 3 } -interface TunnelProperties { - remote: { host: string; port: number }; - local?: number; - name?: string; - source?: { - source: TunnelSource; - description: string; - }; - elevateIfNeeded?: boolean; - privacy?: string; -} - -export enum TunnelSource { - User, - Auto, - Extension -} - -export const UserTunnelSource = { - source: TunnelSource.User, - description: nls.localize('tunnel.source.user', "User Forwarded") -}; -export const AutoTunnelSource = { - source: TunnelSource.Auto, - description: nls.localize('tunnel.source.auto', "Auto Forwarded") -}; - -export interface Tunnel { - remoteHost: string; - remotePort: number; - localAddress: string; - localUri: URI; - protocol: TunnelProtocol; - localPort?: number; - name?: string; - closeable?: boolean; - privacy: TunnelPrivacyId | string; - runningProcess: string | undefined; - hasRunningProcess?: boolean; - pid: number | undefined; - source: { - source: TunnelSource; - description: string; - }; -} - -export function makeAddress(host: string, port: number): string { - return host + ':' + port; -} - -export function parseAddress(address: string): { host: string; port: number } | undefined { - const matches = address.match(/^([a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*:)?([0-9]+)$/); - if (!matches) { - return undefined; - } - return { host: matches[1]?.substring(0, matches[1].length - 1) || 'localhost', port: Number(matches[2]) }; -} - -export function mapHasAddress(map: Map, host: string, port: number): T | undefined { - const initialAddress = map.get(makeAddress(host, port)); - if (initialAddress) { - return initialAddress; - } - - if (isLocalhost(host)) { - // Do localhost checks - for (const testHost of LOCALHOST_ADDRESSES) { - const testAddress = makeAddress(testHost, port); - if (map.has(testAddress)) { - return map.get(testAddress); - } - } - } else if (isAllInterfaces(host)) { - // Do all interfaces checks - for (const testHost of ALL_INTERFACES_ADDRESSES) { - const testAddress = makeAddress(testHost, port); - if (map.has(testAddress)) { - return map.get(testAddress); - } - } - } - - return undefined; -} - -export function mapHasAddressLocalhostOrAllInterfaces(map: Map, host: string, port: number): T | undefined { - const originalAddress = mapHasAddress(map, host, port); - if (originalAddress) { - return originalAddress; - } - const otherHost = isAllInterfaces(host) ? 'localhost' : (isLocalhost(host) ? '0.0.0.0' : undefined); - if (otherHost) { - return mapHasAddress(map, otherHost, port); - } - return undefined; -} - -export enum OnPortForward { - Notify = 'notify', - OpenBrowser = 'openBrowser', - OpenBrowserOnce = 'openBrowserOnce', - OpenPreview = 'openPreview', - Silent = 'silent', - Ignore = 'ignore' -} - -export interface Attributes { - label: string | undefined; - onAutoForward: OnPortForward | undefined; - elevateIfNeeded: boolean | undefined; - requireLocalPort: boolean | undefined; - protocol: TunnelProtocol | undefined; -} - -interface PortRange { start: number; end: number } - -interface HostAndPort { host: string; port: number } - -interface PortAttributes extends Attributes { - key: number | PortRange | RegExp | HostAndPort; -} - -export class PortsAttributes extends Disposable { - private static SETTING = 'remote.portsAttributes'; - private static DEFAULTS = 'remote.otherPortsAttributes'; - private static RANGE = /^(\d+)\-(\d+)$/; - private static HOST_AND_PORT = /^([a-z0-9\-]+):(\d{1,5})$/; - private portsAttributes: PortAttributes[] = []; - private defaultPortAttributes: Attributes | undefined; - private _onDidChangeAttributes = new Emitter(); - public readonly onDidChangeAttributes = this._onDidChangeAttributes.event; - - constructor(private readonly configurationService: IConfigurationService) { - super(); - this._register(configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(PortsAttributes.SETTING) || e.affectsConfiguration(PortsAttributes.DEFAULTS)) { - this.updateAttributes(); - } - })); - this.updateAttributes(); - } - - private updateAttributes() { - this.portsAttributes = this.readSetting(); - this._onDidChangeAttributes.fire(); - } - - getAttributes(port: number, host: string, commandLine?: string): Attributes | undefined { - let index = this.findNextIndex(port, host, commandLine, this.portsAttributes, 0); - const attributes: Attributes = { - label: undefined, - onAutoForward: undefined, - elevateIfNeeded: undefined, - requireLocalPort: undefined, - protocol: undefined - }; - while (index >= 0) { - const found = this.portsAttributes[index]; - if (found.key === port) { - attributes.onAutoForward = found.onAutoForward ?? attributes.onAutoForward; - attributes.elevateIfNeeded = (found.elevateIfNeeded !== undefined) ? found.elevateIfNeeded : attributes.elevateIfNeeded; - attributes.label = found.label ?? attributes.label; - attributes.requireLocalPort = found.requireLocalPort; - attributes.protocol = found.protocol; - } else { - // It's a range or regex, which means that if the attribute is already set, we keep it - attributes.onAutoForward = attributes.onAutoForward ?? found.onAutoForward; - attributes.elevateIfNeeded = (attributes.elevateIfNeeded !== undefined) ? attributes.elevateIfNeeded : found.elevateIfNeeded; - attributes.label = attributes.label ?? found.label; - attributes.requireLocalPort = (attributes.requireLocalPort !== undefined) ? attributes.requireLocalPort : undefined; - attributes.protocol = attributes.protocol ?? found.protocol; - } - index = this.findNextIndex(port, host, commandLine, this.portsAttributes, index + 1); - } - if (attributes.onAutoForward !== undefined || attributes.elevateIfNeeded !== undefined - || attributes.label !== undefined || attributes.requireLocalPort !== undefined - || attributes.protocol !== undefined) { - return attributes; - } - - // If we find no matches, then use the other port attributes. - return this.getOtherAttributes(); - } - - private hasStartEnd(value: number | PortRange | RegExp | HostAndPort): value is PortRange { - return ((value).start !== undefined) && ((value).end !== undefined); - } - - private hasHostAndPort(value: number | PortRange | RegExp | HostAndPort): value is HostAndPort { - return ((value).host !== undefined) && ((value).port !== undefined) - && isString((value).host) && isNumber((value).port); - } - - private findNextIndex(port: number, host: string, commandLine: string | undefined, attributes: PortAttributes[], fromIndex: number): number { - if (fromIndex >= attributes.length) { - return -1; - } - const shouldUseHost = !isLocalhost(host) && !isAllInterfaces(host); - const sliced = attributes.slice(fromIndex); - const foundIndex = sliced.findIndex((value) => { - if (isNumber(value.key)) { - return shouldUseHost ? false : value.key === port; - } else if (this.hasStartEnd(value.key)) { - return shouldUseHost ? false : (port >= value.key.start && port <= value.key.end); - } else if (this.hasHostAndPort(value.key)) { - return (port === value.key.port) && (host === value.key.host); - } else { - return commandLine ? value.key.test(commandLine) : false; - } - - }); - return foundIndex >= 0 ? foundIndex + fromIndex : -1; - } - - private readSetting(): PortAttributes[] { - const settingValue = this.configurationService.getValue(PortsAttributes.SETTING); - if (!settingValue || !isObject(settingValue)) { - return []; - } - - const attributes: PortAttributes[] = []; - for (const attributesKey in settingValue) { - if (attributesKey === undefined) { - continue; - } - const setting = (settingValue)[attributesKey]; - let key: number | PortRange | RegExp | HostAndPort | undefined = undefined; - if (Number(attributesKey)) { - key = Number(attributesKey); - } else if (isString(attributesKey)) { - if (PortsAttributes.RANGE.test(attributesKey)) { - const match = attributesKey.match(PortsAttributes.RANGE); - key = { start: Number(match![1]), end: Number(match![2]) }; - } else if (PortsAttributes.HOST_AND_PORT.test(attributesKey)) { - const match = attributesKey.match(PortsAttributes.HOST_AND_PORT); - key = { host: match![1], port: Number(match![2]) }; - } else { - let regTest: RegExp | undefined = undefined; - try { - regTest = RegExp(attributesKey); - } catch (e) { - // The user entered an invalid regular expression. - } - if (regTest) { - key = regTest; - } - } - } - if (!key) { - continue; - } - attributes.push({ - key: key, - elevateIfNeeded: setting.elevateIfNeeded, - onAutoForward: setting.onAutoForward, - label: setting.label, - requireLocalPort: setting.requireLocalPort, - protocol: setting.protocol - }); - } - - const defaults = this.configurationService.getValue(PortsAttributes.DEFAULTS); - if (defaults) { - this.defaultPortAttributes = { - elevateIfNeeded: defaults.elevateIfNeeded, - label: defaults.label, - onAutoForward: defaults.onAutoForward, - requireLocalPort: defaults.requireLocalPort, - protocol: defaults.protocol - }; - } - - return this.sortAttributes(attributes); - } - - private sortAttributes(attributes: PortAttributes[]): PortAttributes[] { - function getVal(item: PortAttributes, thisRef: PortsAttributes) { - if (isNumber(item.key)) { - return item.key; - } else if (thisRef.hasStartEnd(item.key)) { - return item.key.start; - } else if (thisRef.hasHostAndPort(item.key)) { - return item.key.port; - } else { - return Number.MAX_VALUE; - } - } - - return attributes.sort((a, b) => { - return getVal(a, this) - getVal(b, this); - }); - } - - private getOtherAttributes() { - return this.defaultPortAttributes; - } - - static providedActionToAction(providedAction: ProvidedOnAutoForward | undefined) { - switch (providedAction) { - case ProvidedOnAutoForward.Notify: return OnPortForward.Notify; - case ProvidedOnAutoForward.OpenBrowser: return OnPortForward.OpenBrowser; - case ProvidedOnAutoForward.OpenBrowserOnce: return OnPortForward.OpenBrowserOnce; - case ProvidedOnAutoForward.OpenPreview: return OnPortForward.OpenPreview; - case ProvidedOnAutoForward.Silent: return OnPortForward.Silent; - case ProvidedOnAutoForward.Ignore: return OnPortForward.Ignore; - default: return undefined; - } - } - - public async addAttributes(port: number, attributes: Partial, target: ConfigurationTarget) { - const settingValue = this.configurationService.inspect(PortsAttributes.SETTING); - const remoteValue: any = settingValue.userRemoteValue; - let newRemoteValue: any; - if (!remoteValue || !isObject(remoteValue)) { - newRemoteValue = {}; - } else { - newRemoteValue = deepClone(remoteValue); - } - - if (!newRemoteValue[`${port}`]) { - newRemoteValue[`${port}`] = {}; - } - for (const attribute in attributes) { - newRemoteValue[`${port}`][attribute] = (attributes)[attribute]; - } - - return this.configurationService.updateValue(PortsAttributes.SETTING, newRemoteValue, target); - } -} - -const MISMATCH_LOCAL_PORT_COOLDOWN = 10 * 1000; // 10 seconds - -export class TunnelModel extends Disposable { - readonly forwarded: Map; - private readonly inProgress: Map = new Map(); - readonly detected: Map; - private remoteTunnels: Map; - private _onForwardPort: Emitter = new Emitter(); - public onForwardPort: Event = this._onForwardPort.event; - private _onClosePort: Emitter<{ host: string; port: number }> = new Emitter(); - public onClosePort: Event<{ host: string; port: number }> = this._onClosePort.event; - private _onPortName: Emitter<{ host: string; port: number }> = new Emitter(); - public onPortName: Event<{ host: string; port: number }> = this._onPortName.event; - private _candidates: Map | undefined; - private _onCandidatesChanged: Emitter> = new Emitter(); - // onCandidateChanged returns the removed candidates - public onCandidatesChanged: Event> = this._onCandidatesChanged.event; - private _candidateFilter: ((candidates: CandidatePort[]) => Promise) | undefined; - private tunnelRestoreValue: Promise; - private _onEnvironmentTunnelsSet: Emitter = new Emitter(); - public onEnvironmentTunnelsSet: Event = this._onEnvironmentTunnelsSet.event; - private _environmentTunnelsSet: boolean = false; - public readonly configPortsAttributes: PortsAttributes; - private restoreListener: DisposableStore | undefined = undefined; - private knownPortsRestoreValue: string | undefined; - private unrestoredExtensionTunnels: Map = new Map(); - private sessionCachedProperties: Map> = new Map(); - - private portAttributesProviders: PortAttributesProvider[] = []; - - constructor( - @ITunnelService private readonly tunnelService: ITunnelService, - @IStorageService private readonly storageService: IStorageService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @ILogService private readonly logService: ILogService, - @IDialogService private readonly dialogService: IDialogService - ) { - super(); - this.configPortsAttributes = new PortsAttributes(configurationService); - this.tunnelRestoreValue = this.getTunnelRestoreValue(); - this._register(this.configPortsAttributes.onDidChangeAttributes(this.updateAttributes, this)); - this.forwarded = new Map(); - this.remoteTunnels = new Map(); - this.tunnelService.tunnels.then(async (tunnels) => { - const attributes = await this.getAttributes(tunnels.map(tunnel => { - return { port: tunnel.tunnelRemotePort, host: tunnel.tunnelRemoteHost }; - })); - for (const tunnel of tunnels) { - if (tunnel.localAddress) { - const key = makeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort); - const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces(this._candidates ?? new Map(), tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort); - this.forwarded.set(key, { - remotePort: tunnel.tunnelRemotePort, - remoteHost: tunnel.tunnelRemoteHost, - localAddress: tunnel.localAddress, - protocol: attributes?.get(tunnel.tunnelRemotePort)?.protocol ?? TunnelProtocol.Http, - localUri: await this.makeLocalUri(tunnel.localAddress, attributes?.get(tunnel.tunnelRemotePort)), - localPort: tunnel.tunnelLocalPort, - runningProcess: matchingCandidate?.detail, - hasRunningProcess: !!matchingCandidate, - pid: matchingCandidate?.pid, - privacy: tunnel.privacy, - source: UserTunnelSource, - }); - this.remoteTunnels.set(key, tunnel); - } - } - }); - - this.detected = new Map(); - this._register(this.tunnelService.onTunnelOpened(async (tunnel) => { - const key = makeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort); - if (!mapHasAddressLocalhostOrAllInterfaces(this.forwarded, tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort) - && !mapHasAddressLocalhostOrAllInterfaces(this.detected, tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort) - && !mapHasAddressLocalhostOrAllInterfaces(this.inProgress, tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort) - && tunnel.localAddress) { - const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces(this._candidates ?? new Map(), tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort); - const attributes = (await this.getAttributes([{ port: tunnel.tunnelRemotePort, host: tunnel.tunnelRemoteHost }]))?.get(tunnel.tunnelRemotePort); - this.forwarded.set(key, { - remoteHost: tunnel.tunnelRemoteHost, - remotePort: tunnel.tunnelRemotePort, - localAddress: tunnel.localAddress, - protocol: attributes?.protocol ?? TunnelProtocol.Http, - localUri: await this.makeLocalUri(tunnel.localAddress, attributes), - localPort: tunnel.tunnelLocalPort, - closeable: true, - runningProcess: matchingCandidate?.detail, - hasRunningProcess: !!matchingCandidate, - pid: matchingCandidate?.pid, - privacy: tunnel.privacy, - source: UserTunnelSource, - }); - } - await this.storeForwarded(); - this.remoteTunnels.set(key, tunnel); - this._onForwardPort.fire(this.forwarded.get(key)!); - })); - this._register(this.tunnelService.onTunnelClosed(address => { - return this.onTunnelClosed(address, TunnelCloseReason.Other); - })); - } - - private async onTunnelClosed(address: { host: string; port: number }, reason: TunnelCloseReason) { - const key = makeAddress(address.host, address.port); - if (this.forwarded.has(key)) { - this.forwarded.delete(key); - await this.storeForwarded(); - this._onClosePort.fire(address); - } - } - - private makeLocalUri(localAddress: string, attributes?: Attributes) { - if (localAddress.startsWith('http')) { - return URI.parse(localAddress); - } - const protocol = attributes?.protocol ?? 'http'; - return URI.parse(`${protocol}://${localAddress}`); - } - - private async getStorageKey(): Promise { - const workspace = this.workspaceContextService.getWorkspace(); - const workspaceHash = workspace.configuration ? hash(workspace.configuration.path) : (workspace.folders.length > 0 ? hash(workspace.folders[0].uri.path) : undefined); - if (workspaceHash === undefined) { - this.logService.debug('Could not get workspace hash for forwarded ports storage key.'); - return undefined; - } - return `${TUNNELS_TO_RESTORE}.${this.environmentService.remoteAuthority}.${workspaceHash}`; - } - - private async getTunnelRestoreValue(): Promise { - const deprecatedValue = this.storageService.get(TUNNELS_TO_RESTORE, StorageScope.WORKSPACE); - if (deprecatedValue) { - this.storageService.remove(TUNNELS_TO_RESTORE, StorageScope.WORKSPACE); - await this.storeForwarded(); - return deprecatedValue; - } - const storageKey = await this.getStorageKey(); - if (!storageKey) { - return undefined; - } - return this.storageService.get(storageKey, StorageScope.PROFILE); - } - - async restoreForwarded() { - if (this.configurationService.getValue('remote.restoreForwardedPorts')) { - const tunnelRestoreValue = await this.tunnelRestoreValue; - if (tunnelRestoreValue && (tunnelRestoreValue !== this.knownPortsRestoreValue)) { - const tunnels = JSON.parse(tunnelRestoreValue) ?? []; - this.logService.trace(`ForwardedPorts: (TunnelModel) restoring ports ${tunnels.map(tunnel => tunnel.remotePort).join(', ')}`); - for (const tunnel of tunnels) { - const alreadyForwarded = mapHasAddressLocalhostOrAllInterfaces(this.detected, tunnel.remoteHost, tunnel.remotePort); - // Extension forwarded ports should only be updated, not restored. - if ((tunnel.source.source === TunnelSource.User && !alreadyForwarded) || (tunnel.source.source === TunnelSource.Extension && alreadyForwarded)) { - await this.forward({ - remote: { host: tunnel.remoteHost, port: tunnel.remotePort }, - local: tunnel.localPort, - name: tunnel.name, - privacy: tunnel.privacy, - elevateIfNeeded: true - }); - } else if (tunnel.source.source === TunnelSource.Extension && !alreadyForwarded) { - this.unrestoredExtensionTunnels.set(makeAddress(tunnel.remoteHost, tunnel.remotePort), tunnel); - } - } - } - } - - if (!this.restoreListener) { - // It's possible that at restore time the value hasn't synced. - const key = await this.getStorageKey(); - this.restoreListener = this._register(new DisposableStore()); - this.restoreListener.add(this.storageService.onDidChangeValue(StorageScope.PROFILE, undefined, this.restoreListener)(async (e) => { - if (e.key === key) { - this.tunnelRestoreValue = Promise.resolve(this.storageService.get(key, StorageScope.PROFILE)); - await this.restoreForwarded(); - } - })); - } - } - - @debounce(1000) - private async storeForwarded() { - if (this.configurationService.getValue('remote.restoreForwardedPorts')) { - const valueToStore = JSON.stringify(Array.from(this.forwarded.values()).filter(value => value.source.source !== TunnelSource.Auto)); - if (valueToStore !== this.knownPortsRestoreValue) { - this.knownPortsRestoreValue = valueToStore; - const key = await this.getStorageKey(); - if (key) { - this.storageService.store(key, this.knownPortsRestoreValue, StorageScope.PROFILE, StorageTarget.USER); - } - } - } - } - - private mismatchCooldown = new Date(); - private async showPortMismatchModalIfNeeded(tunnel: RemoteTunnel, expectedLocal: number, attributes: Attributes | undefined) { - if (!tunnel.tunnelLocalPort || !attributes?.requireLocalPort) { - return; - } - if (tunnel.tunnelLocalPort === expectedLocal) { - return; - } - - const newCooldown = new Date(); - if ((this.mismatchCooldown.getTime() + MISMATCH_LOCAL_PORT_COOLDOWN) > newCooldown.getTime()) { - return; - } - this.mismatchCooldown = newCooldown; - const mismatchString = nls.localize('remote.localPortMismatch.single', "Local port {0} could not be used for forwarding to remote port {1}.\n\nThis usually happens when there is already another process using local port {0}.\n\nPort number {2} has been used instead.", - expectedLocal, tunnel.tunnelRemotePort, tunnel.tunnelLocalPort); - return this.dialogService.info(mismatchString); - } - - async forward(tunnelProperties: TunnelProperties, attributes?: Attributes | null): Promise { - const existingTunnel = mapHasAddressLocalhostOrAllInterfaces(this.forwarded, tunnelProperties.remote.host, tunnelProperties.remote.port); - attributes = attributes ?? - ((attributes !== null) - ? (await this.getAttributes([tunnelProperties.remote]))?.get(tunnelProperties.remote.port) - : undefined); - const localPort = (tunnelProperties.local !== undefined) ? tunnelProperties.local : tunnelProperties.remote.port; - - if (!existingTunnel) { - const authority = this.environmentService.remoteAuthority; - const addressProvider: IAddressProvider | undefined = authority ? { - getAddress: async () => { return (await this.remoteAuthorityResolverService.resolveAuthority(authority)).authority; } - } : undefined; - - const key = makeAddress(tunnelProperties.remote.host, tunnelProperties.remote.port); - this.inProgress.set(key, true); - tunnelProperties = this.mergeCachedAndUnrestoredProperties(key, tunnelProperties); - - const tunnel = await this.tunnelService.openTunnel(addressProvider, tunnelProperties.remote.host, tunnelProperties.remote.port, undefined, localPort, (!tunnelProperties.elevateIfNeeded) ? attributes?.elevateIfNeeded : tunnelProperties.elevateIfNeeded, tunnelProperties.privacy, attributes?.protocol); - if (tunnel && tunnel.localAddress) { - const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces(this._candidates ?? new Map(), tunnelProperties.remote.host, tunnelProperties.remote.port); - const protocol = (tunnel.protocol ? - ((tunnel.protocol === TunnelProtocol.Https) ? TunnelProtocol.Https : TunnelProtocol.Http) - : (attributes?.protocol ?? TunnelProtocol.Http)); - const newForward: Tunnel = { - remoteHost: tunnel.tunnelRemoteHost, - remotePort: tunnel.tunnelRemotePort, - localPort: tunnel.tunnelLocalPort, - name: attributes?.label ?? tunnelProperties.name, - closeable: true, - localAddress: tunnel.localAddress, - protocol, - localUri: await this.makeLocalUri(tunnel.localAddress, attributes), - runningProcess: matchingCandidate?.detail, - hasRunningProcess: !!matchingCandidate, - pid: matchingCandidate?.pid, - source: tunnelProperties.source ?? UserTunnelSource, - privacy: tunnel.privacy, - }; - this.forwarded.set(key, newForward); - this.remoteTunnels.set(key, tunnel); - this.inProgress.delete(key); - await this.storeForwarded(); - await this.showPortMismatchModalIfNeeded(tunnel, localPort, attributes); - this._onForwardPort.fire(newForward); - return tunnel; - } - this.inProgress.delete(key); - } else { - return this.mergeAttributesIntoExistingTunnel(existingTunnel, tunnelProperties, attributes); - } - - return undefined; - } - - private mergeCachedAndUnrestoredProperties(key: string, tunnelProperties: TunnelProperties): TunnelProperties { - const map = this.unrestoredExtensionTunnels.has(key) ? this.unrestoredExtensionTunnels : (this.sessionCachedProperties.has(key) ? this.sessionCachedProperties : undefined); - if (map) { - const updateProps = map.get(key)!; - map.delete(key); - if (updateProps) { - tunnelProperties.name = updateProps.name ?? tunnelProperties.name; - tunnelProperties.local = (('local' in updateProps) ? updateProps.local : (('localPort' in updateProps) ? updateProps.localPort : undefined)) ?? tunnelProperties.local; - tunnelProperties.privacy = updateProps.privacy ?? tunnelProperties.privacy; - } - } - return tunnelProperties; - } - - private async mergeAttributesIntoExistingTunnel(existingTunnel: Tunnel, tunnelProperties: TunnelProperties, attributes: Attributes | undefined) { - const newName = attributes?.label ?? tunnelProperties.name; - enum MergedAttributeAction { - None = 0, - Fire = 1, - Reopen = 2 - } - let mergedAction = MergedAttributeAction.None; - if (newName !== existingTunnel.name) { - existingTunnel.name = newName; - mergedAction = MergedAttributeAction.Fire; - } - // Source of existing tunnel wins so that original source is maintained - if ((attributes?.protocol || (existingTunnel.protocol !== TunnelProtocol.Http)) && (attributes?.protocol !== existingTunnel.protocol)) { - tunnelProperties.source = existingTunnel.source; - mergedAction = MergedAttributeAction.Reopen; - } - // New privacy value wins - if (tunnelProperties.privacy && (existingTunnel.privacy !== tunnelProperties.privacy)) { - mergedAction = MergedAttributeAction.Reopen; - } - switch (mergedAction) { - case MergedAttributeAction.Fire: { - this._onForwardPort.fire(); - break; - } - case MergedAttributeAction.Reopen: { - await this.close(existingTunnel.remoteHost, existingTunnel.remotePort, TunnelCloseReason.User); - await this.forward(tunnelProperties, attributes); - } - } - - return mapHasAddressLocalhostOrAllInterfaces(this.remoteTunnels, tunnelProperties.remote.host, tunnelProperties.remote.port); - } - - async name(host: string, port: number, name: string) { - const existingForwarded = mapHasAddressLocalhostOrAllInterfaces(this.forwarded, host, port); - const key = makeAddress(host, port); - if (existingForwarded) { - existingForwarded.name = name; - await this.storeForwarded(); - this._onPortName.fire({ host, port }); - return; - } else if (this.detected.has(key)) { - this.detected.get(key)!.name = name; - this._onPortName.fire({ host, port }); - } - } - - async close(host: string, port: number, reason: TunnelCloseReason): Promise { - const key = makeAddress(host, port); - const oldTunnel = this.forwarded.get(key)!; - if ((reason === TunnelCloseReason.AutoForwardEnd) && oldTunnel && (oldTunnel.source.source === TunnelSource.Auto)) { - this.sessionCachedProperties.set(key, { - local: oldTunnel.localPort, - name: oldTunnel.name, - privacy: oldTunnel.privacy, - }); - } - await this.tunnelService.closeTunnel(host, port); - return this.onTunnelClosed({ host, port }, reason); - } - - address(host: string, port: number): string | undefined { - const key = makeAddress(host, port); - return (this.forwarded.get(key) || this.detected.get(key))?.localAddress; - } - - public get environmentTunnelsSet(): boolean { - return this._environmentTunnelsSet; - } - - addEnvironmentTunnels(tunnels: TunnelDescription[] | undefined): void { - if (tunnels) { - for (const tunnel of tunnels) { - const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces(this._candidates ?? new Map(), tunnel.remoteAddress.host, tunnel.remoteAddress.port); - const localAddress = typeof tunnel.localAddress === 'string' ? tunnel.localAddress : makeAddress(tunnel.localAddress.host, tunnel.localAddress.port); - this.detected.set(makeAddress(tunnel.remoteAddress.host, tunnel.remoteAddress.port), { - remoteHost: tunnel.remoteAddress.host, - remotePort: tunnel.remoteAddress.port, - localAddress: localAddress, - protocol: TunnelProtocol.Http, - localUri: this.makeLocalUri(localAddress), - closeable: false, - runningProcess: matchingCandidate?.detail, - hasRunningProcess: !!matchingCandidate, - pid: matchingCandidate?.pid, - privacy: TunnelPrivacyId.ConstantPrivate, - source: { - source: TunnelSource.Extension, - description: nls.localize('tunnel.staticallyForwarded', "Statically Forwarded") - } - }); - this.tunnelService.setEnvironmentTunnel(tunnel.remoteAddress.host, tunnel.remoteAddress.port, localAddress, TunnelPrivacyId.ConstantPrivate, TunnelProtocol.Http); - } - } - this._environmentTunnelsSet = true; - this._onEnvironmentTunnelsSet.fire(); - this._onForwardPort.fire(); - } - - setCandidateFilter(filter: ((candidates: CandidatePort[]) => Promise) | undefined): void { - this._candidateFilter = filter; - } - - async setCandidates(candidates: CandidatePort[]) { - let processedCandidates = candidates; - if (this._candidateFilter) { - // When an extension provides a filter, we do the filtering on the extension host before the candidates are set here. - // However, when the filter doesn't come from an extension we filter here. - processedCandidates = await this._candidateFilter(candidates); - } - const removedCandidates = this.updateInResponseToCandidates(processedCandidates); - this.logService.trace(`ForwardedPorts: (TunnelModel) removed candidates ${Array.from(removedCandidates.values()).map(candidate => candidate.port).join(', ')}`); - this._onCandidatesChanged.fire(removedCandidates); - } - - // Returns removed candidates - private updateInResponseToCandidates(candidates: CandidatePort[]): Map { - const removedCandidates = this._candidates ?? new Map(); - const candidatesMap = new Map(); - this._candidates = candidatesMap; - candidates.forEach(value => { - const addressKey = makeAddress(value.host, value.port); - candidatesMap.set(addressKey, { - host: value.host, - port: value.port, - detail: value.detail, - pid: value.pid - }); - if (removedCandidates.has(addressKey)) { - removedCandidates.delete(addressKey); - } - const forwardedValue = mapHasAddressLocalhostOrAllInterfaces(this.forwarded, value.host, value.port); - if (forwardedValue) { - forwardedValue.runningProcess = value.detail; - forwardedValue.hasRunningProcess = true; - forwardedValue.pid = value.pid; - } - }); - removedCandidates.forEach((_value, key) => { - const parsedAddress = parseAddress(key); - if (!parsedAddress) { - return; - } - const forwardedValue = mapHasAddressLocalhostOrAllInterfaces(this.forwarded, parsedAddress.host, parsedAddress.port); - if (forwardedValue) { - forwardedValue.runningProcess = undefined; - forwardedValue.hasRunningProcess = false; - forwardedValue.pid = undefined; - } - const detectedValue = mapHasAddressLocalhostOrAllInterfaces(this.detected, parsedAddress.host, parsedAddress.port); - if (detectedValue) { - detectedValue.runningProcess = undefined; - detectedValue.hasRunningProcess = false; - detectedValue.pid = undefined; - } - }); - return removedCandidates; - } - - get candidates(): CandidatePort[] { - return this._candidates ? Array.from(this._candidates.values()) : []; - } - - get candidatesOrUndefined(): CandidatePort[] | undefined { - return this._candidates ? this.candidates : undefined; - } - - private async updateAttributes() { - // If the label changes in the attributes, we should update it. - const tunnels = Array.from(this.forwarded.values()); - const allAttributes = await this.getAttributes(tunnels.map(tunnel => { - return { port: tunnel.remotePort, host: tunnel.remoteHost }; - }), false); - if (!allAttributes) { - return; - } - for (const forwarded of tunnels) { - const attributes = allAttributes.get(forwarded.remotePort); - if ((attributes?.protocol || (forwarded.protocol !== TunnelProtocol.Http)) && (attributes?.protocol !== forwarded.protocol)) { - await this.forward({ - remote: { host: forwarded.remoteHost, port: forwarded.remotePort }, - local: forwarded.localPort, - name: forwarded.name, - source: forwarded.source - }, attributes); - } - - if (!attributes) { - continue; - } - if (attributes.label && attributes.label !== forwarded.name) { - await this.name(forwarded.remoteHost, forwarded.remotePort, attributes.label); - } - - } - } - - async getAttributes(forwardedPorts: { host: string; port: number }[], checkProviders: boolean = true): Promise | undefined> { - const matchingCandidates: Map = new Map(); - const pidToPortsMapping: Map = new Map(); - forwardedPorts.forEach(forwardedPort => { - const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces(this._candidates ?? new Map(), LOCALHOST_ADDRESSES[0], forwardedPort.port) ?? forwardedPort; - if (matchingCandidate) { - matchingCandidates.set(forwardedPort.port, matchingCandidate); - const pid = isCandidatePort(matchingCandidate) ? matchingCandidate.pid : undefined; - if (!pidToPortsMapping.has(pid)) { - pidToPortsMapping.set(pid, []); - } - pidToPortsMapping.get(pid)?.push(forwardedPort.port); - } - }); - - const configAttributes: Map = new Map(); - forwardedPorts.forEach(forwardedPort => { - const attributes = this.configPortsAttributes.getAttributes(forwardedPort.port, forwardedPort.host, matchingCandidates.get(forwardedPort.port)?.detail); - if (attributes) { - configAttributes.set(forwardedPort.port, attributes); - } - }); - if ((this.portAttributesProviders.length === 0) || !checkProviders) { - return (configAttributes.size > 0) ? configAttributes : undefined; - } - - // Group calls to provide attributes by pid. - const allProviderResults = await Promise.all(flatten(this.portAttributesProviders.map(provider => { - return Array.from(pidToPortsMapping.entries()).map(entry => { - const portGroup = entry[1]; - const matchingCandidate = matchingCandidates.get(portGroup[0]); - return provider.providePortAttributes(portGroup, - matchingCandidate?.pid, matchingCandidate?.detail, new CancellationTokenSource().token); - }); - }))); - const providedAttributes: Map = new Map(); - allProviderResults.forEach(attributes => attributes.forEach(attribute => { - if (attribute) { - providedAttributes.set(attribute.port, attribute); - } - })); - - if (!configAttributes && !providedAttributes) { - return undefined; - } - - // Merge. The config wins. - const mergedAttributes: Map = new Map(); - forwardedPorts.forEach(forwardedPorts => { - const config = configAttributes.get(forwardedPorts.port); - const provider = providedAttributes.get(forwardedPorts.port); - mergedAttributes.set(forwardedPorts.port, { - elevateIfNeeded: config?.elevateIfNeeded, - label: config?.label, - onAutoForward: config?.onAutoForward ?? PortsAttributes.providedActionToAction(provider?.autoForwardAction), - requireLocalPort: config?.requireLocalPort, - protocol: config?.protocol - }); - }); - - return mergedAttributes; - } - - addAttributesProvider(provider: PortAttributesProvider) { - this.portAttributesProviders.push(provider); - } -} - -export interface CandidatePort { - host: string; - port: number; - detail?: string; - pid?: number; -} - -export function isCandidatePort(candidate: any): candidate is CandidatePort { - return candidate && 'host' in candidate && typeof candidate.host === 'string' - && 'port' in candidate && typeof candidate.port === 'number' - && (!('detail' in candidate) || typeof candidate.detail === 'string') - && (!('pid' in candidate) || typeof candidate.pid === 'string'); -} - export interface IRemoteExplorerService { readonly _serviceBrand: undefined; onDidChangeTargetType: Event; @@ -1009,15 +94,9 @@ class RemoteExplorerService implements IRemoteExplorerService { constructor( @IStorageService private readonly storageService: IStorageService, @ITunnelService private readonly tunnelService: ITunnelService, - @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, - @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, - @ILogService logService: ILogService, - @IDialogService dialogService: IDialogService + @IInstantiationService instantiationService: IInstantiationService, ) { - this._tunnelModel = new TunnelModel(tunnelService, storageService, configurationService, environmentService, - remoteAuthorityResolverService, workspaceContextService, logService, dialogService); + this._tunnelModel = instantiationService.createInstance(TunnelModel); } set targetType(name: string[]) { diff --git a/src/vs/workbench/services/remote/common/tunnelModel.ts b/src/vs/workbench/services/remote/common/tunnelModel.ts new file mode 100644 index 00000000000..4c75de1e7a9 --- /dev/null +++ b/src/vs/workbench/services/remote/common/tunnelModel.ts @@ -0,0 +1,932 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { flatten } from 'vs/base/common/arrays'; +import { debounce } from 'vs/base/common/decorators'; +import { Emitter, Event } from 'vs/base/common/event'; +import { hash } from 'vs/base/common/hash'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnection'; +import { IRemoteAuthorityResolverService, TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { RemoteTunnel, ITunnelService, TunnelProtocol, TunnelPrivacyId, LOCALHOST_ADDRESSES, ProvidedPortAttributes, PortAttributesProvider, isLocalhost, isAllInterfaces, ProvidedOnAutoForward, ALL_INTERFACES_ADDRESSES } from 'vs/platform/tunnel/common/tunnel'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { isNumber, isObject, isString } from 'vs/base/common/types'; +import { deepClone } from 'vs/base/common/objects'; + +const MISMATCH_LOCAL_PORT_COOLDOWN = 10 * 1000; // 10 seconds +const TUNNELS_TO_RESTORE = 'remote.tunnels.toRestore'; + +export interface Tunnel { + remoteHost: string; + remotePort: number; + localAddress: string; + localUri: URI; + protocol: TunnelProtocol; + localPort?: number; + name?: string; + closeable?: boolean; + privacy: TunnelPrivacyId | string; + runningProcess: string | undefined; + hasRunningProcess?: boolean; + pid: number | undefined; + source: { + source: TunnelSource; + description: string; + }; +} + +export function parseAddress(address: string): { host: string; port: number } | undefined { + const matches = address.match(/^([a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*:)?([0-9]+)$/); + if (!matches) { + return undefined; + } + return { host: matches[1]?.substring(0, matches[1].length - 1) || 'localhost', port: Number(matches[2]) }; +} + +export enum TunnelCloseReason { + Other = 'Other', + User = 'User', + AutoForwardEnd = 'AutoForwardEnd', +} + +export enum TunnelSource { + User, + Auto, + Extension +} + +export const UserTunnelSource = { + source: TunnelSource.User, + description: nls.localize('tunnel.source.user', "User Forwarded") +}; +export const AutoTunnelSource = { + source: TunnelSource.Auto, + description: nls.localize('tunnel.source.auto', "Auto Forwarded") +}; + +export function mapHasAddress(map: Map, host: string, port: number): T | undefined { + const initialAddress = map.get(makeAddress(host, port)); + if (initialAddress) { + return initialAddress; + } + + if (isLocalhost(host)) { + // Do localhost checks + for (const testHost of LOCALHOST_ADDRESSES) { + const testAddress = makeAddress(testHost, port); + if (map.has(testAddress)) { + return map.get(testAddress); + } + } + } else if (isAllInterfaces(host)) { + // Do all interfaces checks + for (const testHost of ALL_INTERFACES_ADDRESSES) { + const testAddress = makeAddress(testHost, port); + if (map.has(testAddress)) { + return map.get(testAddress); + } + } + } + + return undefined; +} + +export function mapHasAddressLocalhostOrAllInterfaces(map: Map, host: string, port: number): T | undefined { + const originalAddress = mapHasAddress(map, host, port); + if (originalAddress) { + return originalAddress; + } + const otherHost = isAllInterfaces(host) ? 'localhost' : (isLocalhost(host) ? '0.0.0.0' : undefined); + if (otherHost) { + return mapHasAddress(map, otherHost, port); + } + return undefined; +} + + +export function makeAddress(host: string, port: number): string { + return host + ':' + port; +} + +export interface TunnelProperties { + remote: { host: string; port: number }; + local?: number; + name?: string; + source?: { + source: TunnelSource; + description: string; + }; + elevateIfNeeded?: boolean; + privacy?: string; +} + +export interface CandidatePort { + host: string; + port: number; + detail?: string; + pid?: number; +} + +interface PortAttributes extends Attributes { + key: number | PortRange | RegExp | HostAndPort; +} + +export enum OnPortForward { + Notify = 'notify', + OpenBrowser = 'openBrowser', + OpenBrowserOnce = 'openBrowserOnce', + OpenPreview = 'openPreview', + Silent = 'silent', + Ignore = 'ignore' +} + +export interface Attributes { + label: string | undefined; + onAutoForward: OnPortForward | undefined; + elevateIfNeeded: boolean | undefined; + requireLocalPort: boolean | undefined; + protocol: TunnelProtocol | undefined; +} + +interface PortRange { start: number; end: number } + +interface HostAndPort { host: string; port: number } + +export function isCandidatePort(candidate: any): candidate is CandidatePort { + return candidate && 'host' in candidate && typeof candidate.host === 'string' + && 'port' in candidate && typeof candidate.port === 'number' + && (!('detail' in candidate) || typeof candidate.detail === 'string') + && (!('pid' in candidate) || typeof candidate.pid === 'string'); +} + +export class PortsAttributes extends Disposable { + private static SETTING = 'remote.portsAttributes'; + private static DEFAULTS = 'remote.otherPortsAttributes'; + private static RANGE = /^(\d+)\-(\d+)$/; + private static HOST_AND_PORT = /^([a-z0-9\-]+):(\d{1,5})$/; + private portsAttributes: PortAttributes[] = []; + private defaultPortAttributes: Attributes | undefined; + private _onDidChangeAttributes = new Emitter(); + public readonly onDidChangeAttributes = this._onDidChangeAttributes.event; + + constructor(private readonly configurationService: IConfigurationService) { + super(); + this._register(configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(PortsAttributes.SETTING) || e.affectsConfiguration(PortsAttributes.DEFAULTS)) { + this.updateAttributes(); + } + })); + this.updateAttributes(); + } + + private updateAttributes() { + this.portsAttributes = this.readSetting(); + this._onDidChangeAttributes.fire(); + } + + getAttributes(port: number, host: string, commandLine?: string): Attributes | undefined { + let index = this.findNextIndex(port, host, commandLine, this.portsAttributes, 0); + const attributes: Attributes = { + label: undefined, + onAutoForward: undefined, + elevateIfNeeded: undefined, + requireLocalPort: undefined, + protocol: undefined + }; + while (index >= 0) { + const found = this.portsAttributes[index]; + if (found.key === port) { + attributes.onAutoForward = found.onAutoForward ?? attributes.onAutoForward; + attributes.elevateIfNeeded = (found.elevateIfNeeded !== undefined) ? found.elevateIfNeeded : attributes.elevateIfNeeded; + attributes.label = found.label ?? attributes.label; + attributes.requireLocalPort = found.requireLocalPort; + attributes.protocol = found.protocol; + } else { + // It's a range or regex, which means that if the attribute is already set, we keep it + attributes.onAutoForward = attributes.onAutoForward ?? found.onAutoForward; + attributes.elevateIfNeeded = (attributes.elevateIfNeeded !== undefined) ? attributes.elevateIfNeeded : found.elevateIfNeeded; + attributes.label = attributes.label ?? found.label; + attributes.requireLocalPort = (attributes.requireLocalPort !== undefined) ? attributes.requireLocalPort : undefined; + attributes.protocol = attributes.protocol ?? found.protocol; + } + index = this.findNextIndex(port, host, commandLine, this.portsAttributes, index + 1); + } + if (attributes.onAutoForward !== undefined || attributes.elevateIfNeeded !== undefined + || attributes.label !== undefined || attributes.requireLocalPort !== undefined + || attributes.protocol !== undefined) { + return attributes; + } + + // If we find no matches, then use the other port attributes. + return this.getOtherAttributes(); + } + + private hasStartEnd(value: number | PortRange | RegExp | HostAndPort): value is PortRange { + return ((value).start !== undefined) && ((value).end !== undefined); + } + + private hasHostAndPort(value: number | PortRange | RegExp | HostAndPort): value is HostAndPort { + return ((value).host !== undefined) && ((value).port !== undefined) + && isString((value).host) && isNumber((value).port); + } + + private findNextIndex(port: number, host: string, commandLine: string | undefined, attributes: PortAttributes[], fromIndex: number): number { + if (fromIndex >= attributes.length) { + return -1; + } + const shouldUseHost = !isLocalhost(host) && !isAllInterfaces(host); + const sliced = attributes.slice(fromIndex); + const foundIndex = sliced.findIndex((value) => { + if (isNumber(value.key)) { + return shouldUseHost ? false : value.key === port; + } else if (this.hasStartEnd(value.key)) { + return shouldUseHost ? false : (port >= value.key.start && port <= value.key.end); + } else if (this.hasHostAndPort(value.key)) { + return (port === value.key.port) && (host === value.key.host); + } else { + return commandLine ? value.key.test(commandLine) : false; + } + + }); + return foundIndex >= 0 ? foundIndex + fromIndex : -1; + } + + private readSetting(): PortAttributes[] { + const settingValue = this.configurationService.getValue(PortsAttributes.SETTING); + if (!settingValue || !isObject(settingValue)) { + return []; + } + + const attributes: PortAttributes[] = []; + for (const attributesKey in settingValue) { + if (attributesKey === undefined) { + continue; + } + const setting = (settingValue)[attributesKey]; + let key: number | PortRange | RegExp | HostAndPort | undefined = undefined; + if (Number(attributesKey)) { + key = Number(attributesKey); + } else if (isString(attributesKey)) { + if (PortsAttributes.RANGE.test(attributesKey)) { + const match = attributesKey.match(PortsAttributes.RANGE); + key = { start: Number(match![1]), end: Number(match![2]) }; + } else if (PortsAttributes.HOST_AND_PORT.test(attributesKey)) { + const match = attributesKey.match(PortsAttributes.HOST_AND_PORT); + key = { host: match![1], port: Number(match![2]) }; + } else { + let regTest: RegExp | undefined = undefined; + try { + regTest = RegExp(attributesKey); + } catch (e) { + // The user entered an invalid regular expression. + } + if (regTest) { + key = regTest; + } + } + } + if (!key) { + continue; + } + attributes.push({ + key: key, + elevateIfNeeded: setting.elevateIfNeeded, + onAutoForward: setting.onAutoForward, + label: setting.label, + requireLocalPort: setting.requireLocalPort, + protocol: setting.protocol + }); + } + + const defaults = this.configurationService.getValue(PortsAttributes.DEFAULTS); + if (defaults) { + this.defaultPortAttributes = { + elevateIfNeeded: defaults.elevateIfNeeded, + label: defaults.label, + onAutoForward: defaults.onAutoForward, + requireLocalPort: defaults.requireLocalPort, + protocol: defaults.protocol + }; + } + + return this.sortAttributes(attributes); + } + + private sortAttributes(attributes: PortAttributes[]): PortAttributes[] { + function getVal(item: PortAttributes, thisRef: PortsAttributes) { + if (isNumber(item.key)) { + return item.key; + } else if (thisRef.hasStartEnd(item.key)) { + return item.key.start; + } else if (thisRef.hasHostAndPort(item.key)) { + return item.key.port; + } else { + return Number.MAX_VALUE; + } + } + + return attributes.sort((a, b) => { + return getVal(a, this) - getVal(b, this); + }); + } + + private getOtherAttributes() { + return this.defaultPortAttributes; + } + + static providedActionToAction(providedAction: ProvidedOnAutoForward | undefined) { + switch (providedAction) { + case ProvidedOnAutoForward.Notify: return OnPortForward.Notify; + case ProvidedOnAutoForward.OpenBrowser: return OnPortForward.OpenBrowser; + case ProvidedOnAutoForward.OpenBrowserOnce: return OnPortForward.OpenBrowserOnce; + case ProvidedOnAutoForward.OpenPreview: return OnPortForward.OpenPreview; + case ProvidedOnAutoForward.Silent: return OnPortForward.Silent; + case ProvidedOnAutoForward.Ignore: return OnPortForward.Ignore; + default: return undefined; + } + } + + public async addAttributes(port: number, attributes: Partial, target: ConfigurationTarget) { + const settingValue = this.configurationService.inspect(PortsAttributes.SETTING); + const remoteValue: any = settingValue.userRemoteValue; + let newRemoteValue: any; + if (!remoteValue || !isObject(remoteValue)) { + newRemoteValue = {}; + } else { + newRemoteValue = deepClone(remoteValue); + } + + if (!newRemoteValue[`${port}`]) { + newRemoteValue[`${port}`] = {}; + } + for (const attribute in attributes) { + newRemoteValue[`${port}`][attribute] = (attributes)[attribute]; + } + + return this.configurationService.updateValue(PortsAttributes.SETTING, newRemoteValue, target); + } +} + +export class TunnelModel extends Disposable { + readonly forwarded: Map; + private readonly inProgress: Map = new Map(); + readonly detected: Map; + private remoteTunnels: Map; + private _onForwardPort: Emitter = new Emitter(); + public onForwardPort: Event = this._onForwardPort.event; + private _onClosePort: Emitter<{ host: string; port: number }> = new Emitter(); + public onClosePort: Event<{ host: string; port: number }> = this._onClosePort.event; + private _onPortName: Emitter<{ host: string; port: number }> = new Emitter(); + public onPortName: Event<{ host: string; port: number }> = this._onPortName.event; + private _candidates: Map | undefined; + private _onCandidatesChanged: Emitter> = new Emitter(); + // onCandidateChanged returns the removed candidates + public onCandidatesChanged: Event> = this._onCandidatesChanged.event; + private _candidateFilter: ((candidates: CandidatePort[]) => Promise) | undefined; + private tunnelRestoreValue: Promise; + private _onEnvironmentTunnelsSet: Emitter = new Emitter(); + public onEnvironmentTunnelsSet: Event = this._onEnvironmentTunnelsSet.event; + private _environmentTunnelsSet: boolean = false; + public readonly configPortsAttributes: PortsAttributes; + private restoreListener: DisposableStore | undefined = undefined; + private knownPortsRestoreValue: string | undefined; + private unrestoredExtensionTunnels: Map = new Map(); + private sessionCachedProperties: Map> = new Map(); + + private portAttributesProviders: PortAttributesProvider[] = []; + + constructor( + @ITunnelService private readonly tunnelService: ITunnelService, + @IStorageService private readonly storageService: IStorageService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @ILogService private readonly logService: ILogService, + @IDialogService private readonly dialogService: IDialogService, + @IExtensionService private readonly extensionService: IExtensionService + ) { + super(); + this.configPortsAttributes = new PortsAttributes(configurationService); + this.tunnelRestoreValue = this.getTunnelRestoreValue(); + this._register(this.configPortsAttributes.onDidChangeAttributes(this.updateAttributes, this)); + this.forwarded = new Map(); + this.remoteTunnels = new Map(); + this.tunnelService.tunnels.then(async (tunnels) => { + const attributes = await this.getAttributes(tunnels.map(tunnel => { + return { port: tunnel.tunnelRemotePort, host: tunnel.tunnelRemoteHost }; + })); + for (const tunnel of tunnels) { + if (tunnel.localAddress) { + const key = makeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort); + const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces(this._candidates ?? new Map(), tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort); + this.forwarded.set(key, { + remotePort: tunnel.tunnelRemotePort, + remoteHost: tunnel.tunnelRemoteHost, + localAddress: tunnel.localAddress, + protocol: attributes?.get(tunnel.tunnelRemotePort)?.protocol ?? TunnelProtocol.Http, + localUri: await this.makeLocalUri(tunnel.localAddress, attributes?.get(tunnel.tunnelRemotePort)), + localPort: tunnel.tunnelLocalPort, + runningProcess: matchingCandidate?.detail, + hasRunningProcess: !!matchingCandidate, + pid: matchingCandidate?.pid, + privacy: tunnel.privacy, + source: UserTunnelSource, + }); + this.remoteTunnels.set(key, tunnel); + } + } + }); + + this.detected = new Map(); + this._register(this.tunnelService.onTunnelOpened(async (tunnel) => { + const key = makeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort); + if (!mapHasAddressLocalhostOrAllInterfaces(this.forwarded, tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort) + && !mapHasAddressLocalhostOrAllInterfaces(this.detected, tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort) + && !mapHasAddressLocalhostOrAllInterfaces(this.inProgress, tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort) + && tunnel.localAddress) { + const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces(this._candidates ?? new Map(), tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort); + const attributes = (await this.getAttributes([{ port: tunnel.tunnelRemotePort, host: tunnel.tunnelRemoteHost }]))?.get(tunnel.tunnelRemotePort); + this.forwarded.set(key, { + remoteHost: tunnel.tunnelRemoteHost, + remotePort: tunnel.tunnelRemotePort, + localAddress: tunnel.localAddress, + protocol: attributes?.protocol ?? TunnelProtocol.Http, + localUri: await this.makeLocalUri(tunnel.localAddress, attributes), + localPort: tunnel.tunnelLocalPort, + closeable: true, + runningProcess: matchingCandidate?.detail, + hasRunningProcess: !!matchingCandidate, + pid: matchingCandidate?.pid, + privacy: tunnel.privacy, + source: UserTunnelSource, + }); + } + await this.storeForwarded(); + this.remoteTunnels.set(key, tunnel); + this._onForwardPort.fire(this.forwarded.get(key)!); + })); + this._register(this.tunnelService.onTunnelClosed(address => { + return this.onTunnelClosed(address, TunnelCloseReason.Other); + })); + } + + private async onTunnelClosed(address: { host: string; port: number }, reason: TunnelCloseReason) { + const key = makeAddress(address.host, address.port); + if (this.forwarded.has(key)) { + this.forwarded.delete(key); + await this.storeForwarded(); + this._onClosePort.fire(address); + } + } + + private makeLocalUri(localAddress: string, attributes?: Attributes) { + if (localAddress.startsWith('http')) { + return URI.parse(localAddress); + } + const protocol = attributes?.protocol ?? 'http'; + return URI.parse(`${protocol}://${localAddress}`); + } + + private async getStorageKey(): Promise { + const workspace = this.workspaceContextService.getWorkspace(); + const workspaceHash = workspace.configuration ? hash(workspace.configuration.path) : (workspace.folders.length > 0 ? hash(workspace.folders[0].uri.path) : undefined); + if (workspaceHash === undefined) { + this.logService.debug('Could not get workspace hash for forwarded ports storage key.'); + return undefined; + } + return `${TUNNELS_TO_RESTORE}.${this.environmentService.remoteAuthority}.${workspaceHash}`; + } + + private async getTunnelRestoreValue(): Promise { + const deprecatedValue = this.storageService.get(TUNNELS_TO_RESTORE, StorageScope.WORKSPACE); + if (deprecatedValue) { + this.storageService.remove(TUNNELS_TO_RESTORE, StorageScope.WORKSPACE); + await this.storeForwarded(); + return deprecatedValue; + } + const storageKey = await this.getStorageKey(); + if (!storageKey) { + return undefined; + } + return this.storageService.get(storageKey, StorageScope.PROFILE); + } + + async restoreForwarded() { + if (this.configurationService.getValue('remote.restoreForwardedPorts')) { + const tunnelRestoreValue = await this.tunnelRestoreValue; + if (tunnelRestoreValue && (tunnelRestoreValue !== this.knownPortsRestoreValue)) { + const tunnels = JSON.parse(tunnelRestoreValue) ?? []; + this.logService.trace(`ForwardedPorts: (TunnelModel) restoring ports ${tunnels.map(tunnel => tunnel.remotePort).join(', ')}`); + for (const tunnel of tunnels) { + const alreadyForwarded = mapHasAddressLocalhostOrAllInterfaces(this.detected, tunnel.remoteHost, tunnel.remotePort); + // Extension forwarded ports should only be updated, not restored. + if ((tunnel.source.source === TunnelSource.User && !alreadyForwarded) || (tunnel.source.source === TunnelSource.Extension && alreadyForwarded)) { + await this.forward({ + remote: { host: tunnel.remoteHost, port: tunnel.remotePort }, + local: tunnel.localPort, + name: tunnel.name, + privacy: tunnel.privacy, + elevateIfNeeded: true + }); + } else if (tunnel.source.source === TunnelSource.Extension && !alreadyForwarded) { + this.unrestoredExtensionTunnels.set(makeAddress(tunnel.remoteHost, tunnel.remotePort), tunnel); + } + } + } + } + + if (!this.restoreListener) { + // It's possible that at restore time the value hasn't synced. + const key = await this.getStorageKey(); + this.restoreListener = this._register(new DisposableStore()); + this.restoreListener.add(this.storageService.onDidChangeValue(StorageScope.PROFILE, undefined, this.restoreListener)(async (e) => { + if (e.key === key) { + this.tunnelRestoreValue = Promise.resolve(this.storageService.get(key, StorageScope.PROFILE)); + await this.restoreForwarded(); + } + })); + } + } + + @debounce(1000) + private async storeForwarded() { + if (this.configurationService.getValue('remote.restoreForwardedPorts')) { + const valueToStore = JSON.stringify(Array.from(this.forwarded.values()).filter(value => value.source.source !== TunnelSource.Auto)); + if (valueToStore !== this.knownPortsRestoreValue) { + this.knownPortsRestoreValue = valueToStore; + const key = await this.getStorageKey(); + if (key) { + this.storageService.store(key, this.knownPortsRestoreValue, StorageScope.PROFILE, StorageTarget.USER); + } + } + } + } + + private mismatchCooldown = new Date(); + private async showPortMismatchModalIfNeeded(tunnel: RemoteTunnel, expectedLocal: number, attributes: Attributes | undefined) { + if (!tunnel.tunnelLocalPort || !attributes?.requireLocalPort) { + return; + } + if (tunnel.tunnelLocalPort === expectedLocal) { + return; + } + + const newCooldown = new Date(); + if ((this.mismatchCooldown.getTime() + MISMATCH_LOCAL_PORT_COOLDOWN) > newCooldown.getTime()) { + return; + } + this.mismatchCooldown = newCooldown; + const mismatchString = nls.localize('remote.localPortMismatch.single', "Local port {0} could not be used for forwarding to remote port {1}.\n\nThis usually happens when there is already another process using local port {0}.\n\nPort number {2} has been used instead.", + expectedLocal, tunnel.tunnelRemotePort, tunnel.tunnelLocalPort); + return this.dialogService.info(mismatchString); + } + + async forward(tunnelProperties: TunnelProperties, attributes?: Attributes | null): Promise { + await this.extensionService.activateByEvent('onTunnel'); + + const existingTunnel = mapHasAddressLocalhostOrAllInterfaces(this.forwarded, tunnelProperties.remote.host, tunnelProperties.remote.port); + attributes = attributes ?? + ((attributes !== null) + ? (await this.getAttributes([tunnelProperties.remote]))?.get(tunnelProperties.remote.port) + : undefined); + const localPort = (tunnelProperties.local !== undefined) ? tunnelProperties.local : tunnelProperties.remote.port; + + if (!existingTunnel) { + const authority = this.environmentService.remoteAuthority; + const addressProvider: IAddressProvider | undefined = authority ? { + getAddress: async () => { return (await this.remoteAuthorityResolverService.resolveAuthority(authority)).authority; } + } : undefined; + + const key = makeAddress(tunnelProperties.remote.host, tunnelProperties.remote.port); + this.inProgress.set(key, true); + tunnelProperties = this.mergeCachedAndUnrestoredProperties(key, tunnelProperties); + + const tunnel = await this.tunnelService.openTunnel(addressProvider, tunnelProperties.remote.host, tunnelProperties.remote.port, undefined, localPort, (!tunnelProperties.elevateIfNeeded) ? attributes?.elevateIfNeeded : tunnelProperties.elevateIfNeeded, tunnelProperties.privacy, attributes?.protocol); + if (tunnel && tunnel.localAddress) { + const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces(this._candidates ?? new Map(), tunnelProperties.remote.host, tunnelProperties.remote.port); + const protocol = (tunnel.protocol ? + ((tunnel.protocol === TunnelProtocol.Https) ? TunnelProtocol.Https : TunnelProtocol.Http) + : (attributes?.protocol ?? TunnelProtocol.Http)); + const newForward: Tunnel = { + remoteHost: tunnel.tunnelRemoteHost, + remotePort: tunnel.tunnelRemotePort, + localPort: tunnel.tunnelLocalPort, + name: attributes?.label ?? tunnelProperties.name, + closeable: true, + localAddress: tunnel.localAddress, + protocol, + localUri: await this.makeLocalUri(tunnel.localAddress, attributes), + runningProcess: matchingCandidate?.detail, + hasRunningProcess: !!matchingCandidate, + pid: matchingCandidate?.pid, + source: tunnelProperties.source ?? UserTunnelSource, + privacy: tunnel.privacy, + }; + this.forwarded.set(key, newForward); + this.remoteTunnels.set(key, tunnel); + this.inProgress.delete(key); + await this.storeForwarded(); + await this.showPortMismatchModalIfNeeded(tunnel, localPort, attributes); + this._onForwardPort.fire(newForward); + return tunnel; + } + this.inProgress.delete(key); + } else { + return this.mergeAttributesIntoExistingTunnel(existingTunnel, tunnelProperties, attributes); + } + + return undefined; + } + + private mergeCachedAndUnrestoredProperties(key: string, tunnelProperties: TunnelProperties): TunnelProperties { + const map = this.unrestoredExtensionTunnels.has(key) ? this.unrestoredExtensionTunnels : (this.sessionCachedProperties.has(key) ? this.sessionCachedProperties : undefined); + if (map) { + const updateProps = map.get(key)!; + map.delete(key); + if (updateProps) { + tunnelProperties.name = updateProps.name ?? tunnelProperties.name; + tunnelProperties.local = (('local' in updateProps) ? updateProps.local : (('localPort' in updateProps) ? updateProps.localPort : undefined)) ?? tunnelProperties.local; + tunnelProperties.privacy = updateProps.privacy ?? tunnelProperties.privacy; + } + } + return tunnelProperties; + } + + private async mergeAttributesIntoExistingTunnel(existingTunnel: Tunnel, tunnelProperties: TunnelProperties, attributes: Attributes | undefined) { + const newName = attributes?.label ?? tunnelProperties.name; + enum MergedAttributeAction { + None = 0, + Fire = 1, + Reopen = 2 + } + let mergedAction = MergedAttributeAction.None; + if (newName !== existingTunnel.name) { + existingTunnel.name = newName; + mergedAction = MergedAttributeAction.Fire; + } + // Source of existing tunnel wins so that original source is maintained + if ((attributes?.protocol || (existingTunnel.protocol !== TunnelProtocol.Http)) && (attributes?.protocol !== existingTunnel.protocol)) { + tunnelProperties.source = existingTunnel.source; + mergedAction = MergedAttributeAction.Reopen; + } + // New privacy value wins + if (tunnelProperties.privacy && (existingTunnel.privacy !== tunnelProperties.privacy)) { + mergedAction = MergedAttributeAction.Reopen; + } + switch (mergedAction) { + case MergedAttributeAction.Fire: { + this._onForwardPort.fire(); + break; + } + case MergedAttributeAction.Reopen: { + await this.close(existingTunnel.remoteHost, existingTunnel.remotePort, TunnelCloseReason.User); + await this.forward(tunnelProperties, attributes); + } + } + + return mapHasAddressLocalhostOrAllInterfaces(this.remoteTunnels, tunnelProperties.remote.host, tunnelProperties.remote.port); + } + + async name(host: string, port: number, name: string) { + const existingForwarded = mapHasAddressLocalhostOrAllInterfaces(this.forwarded, host, port); + const key = makeAddress(host, port); + if (existingForwarded) { + existingForwarded.name = name; + await this.storeForwarded(); + this._onPortName.fire({ host, port }); + return; + } else if (this.detected.has(key)) { + this.detected.get(key)!.name = name; + this._onPortName.fire({ host, port }); + } + } + + async close(host: string, port: number, reason: TunnelCloseReason): Promise { + const key = makeAddress(host, port); + const oldTunnel = this.forwarded.get(key)!; + if ((reason === TunnelCloseReason.AutoForwardEnd) && oldTunnel && (oldTunnel.source.source === TunnelSource.Auto)) { + this.sessionCachedProperties.set(key, { + local: oldTunnel.localPort, + name: oldTunnel.name, + privacy: oldTunnel.privacy, + }); + } + await this.tunnelService.closeTunnel(host, port); + return this.onTunnelClosed({ host, port }, reason); + } + + address(host: string, port: number): string | undefined { + const key = makeAddress(host, port); + return (this.forwarded.get(key) || this.detected.get(key))?.localAddress; + } + + public get environmentTunnelsSet(): boolean { + return this._environmentTunnelsSet; + } + + addEnvironmentTunnels(tunnels: TunnelDescription[] | undefined): void { + if (tunnels) { + for (const tunnel of tunnels) { + const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces(this._candidates ?? new Map(), tunnel.remoteAddress.host, tunnel.remoteAddress.port); + const localAddress = typeof tunnel.localAddress === 'string' ? tunnel.localAddress : makeAddress(tunnel.localAddress.host, tunnel.localAddress.port); + this.detected.set(makeAddress(tunnel.remoteAddress.host, tunnel.remoteAddress.port), { + remoteHost: tunnel.remoteAddress.host, + remotePort: tunnel.remoteAddress.port, + localAddress: localAddress, + protocol: TunnelProtocol.Http, + localUri: this.makeLocalUri(localAddress), + closeable: false, + runningProcess: matchingCandidate?.detail, + hasRunningProcess: !!matchingCandidate, + pid: matchingCandidate?.pid, + privacy: TunnelPrivacyId.ConstantPrivate, + source: { + source: TunnelSource.Extension, + description: nls.localize('tunnel.staticallyForwarded', "Statically Forwarded") + } + }); + this.tunnelService.setEnvironmentTunnel(tunnel.remoteAddress.host, tunnel.remoteAddress.port, localAddress, TunnelPrivacyId.ConstantPrivate, TunnelProtocol.Http); + } + } + this._environmentTunnelsSet = true; + this._onEnvironmentTunnelsSet.fire(); + this._onForwardPort.fire(); + } + + setCandidateFilter(filter: ((candidates: CandidatePort[]) => Promise) | undefined): void { + this._candidateFilter = filter; + } + + async setCandidates(candidates: CandidatePort[]) { + let processedCandidates = candidates; + if (this._candidateFilter) { + // When an extension provides a filter, we do the filtering on the extension host before the candidates are set here. + // However, when the filter doesn't come from an extension we filter here. + processedCandidates = await this._candidateFilter(candidates); + } + const removedCandidates = this.updateInResponseToCandidates(processedCandidates); + this.logService.trace(`ForwardedPorts: (TunnelModel) removed candidates ${Array.from(removedCandidates.values()).map(candidate => candidate.port).join(', ')}`); + this._onCandidatesChanged.fire(removedCandidates); + } + + // Returns removed candidates + private updateInResponseToCandidates(candidates: CandidatePort[]): Map { + const removedCandidates = this._candidates ?? new Map(); + const candidatesMap = new Map(); + this._candidates = candidatesMap; + candidates.forEach(value => { + const addressKey = makeAddress(value.host, value.port); + candidatesMap.set(addressKey, { + host: value.host, + port: value.port, + detail: value.detail, + pid: value.pid + }); + if (removedCandidates.has(addressKey)) { + removedCandidates.delete(addressKey); + } + const forwardedValue = mapHasAddressLocalhostOrAllInterfaces(this.forwarded, value.host, value.port); + if (forwardedValue) { + forwardedValue.runningProcess = value.detail; + forwardedValue.hasRunningProcess = true; + forwardedValue.pid = value.pid; + } + }); + removedCandidates.forEach((_value, key) => { + const parsedAddress = parseAddress(key); + if (!parsedAddress) { + return; + } + const forwardedValue = mapHasAddressLocalhostOrAllInterfaces(this.forwarded, parsedAddress.host, parsedAddress.port); + if (forwardedValue) { + forwardedValue.runningProcess = undefined; + forwardedValue.hasRunningProcess = false; + forwardedValue.pid = undefined; + } + const detectedValue = mapHasAddressLocalhostOrAllInterfaces(this.detected, parsedAddress.host, parsedAddress.port); + if (detectedValue) { + detectedValue.runningProcess = undefined; + detectedValue.hasRunningProcess = false; + detectedValue.pid = undefined; + } + }); + return removedCandidates; + } + + get candidates(): CandidatePort[] { + return this._candidates ? Array.from(this._candidates.values()) : []; + } + + get candidatesOrUndefined(): CandidatePort[] | undefined { + return this._candidates ? this.candidates : undefined; + } + + private async updateAttributes() { + // If the label changes in the attributes, we should update it. + const tunnels = Array.from(this.forwarded.values()); + const allAttributes = await this.getAttributes(tunnels.map(tunnel => { + return { port: tunnel.remotePort, host: tunnel.remoteHost }; + }), false); + if (!allAttributes) { + return; + } + for (const forwarded of tunnels) { + const attributes = allAttributes.get(forwarded.remotePort); + if ((attributes?.protocol || (forwarded.protocol !== TunnelProtocol.Http)) && (attributes?.protocol !== forwarded.protocol)) { + await this.forward({ + remote: { host: forwarded.remoteHost, port: forwarded.remotePort }, + local: forwarded.localPort, + name: forwarded.name, + source: forwarded.source + }, attributes); + } + + if (!attributes) { + continue; + } + if (attributes.label && attributes.label !== forwarded.name) { + await this.name(forwarded.remoteHost, forwarded.remotePort, attributes.label); + } + + } + } + + async getAttributes(forwardedPorts: { host: string; port: number }[], checkProviders: boolean = true): Promise | undefined> { + const matchingCandidates: Map = new Map(); + const pidToPortsMapping: Map = new Map(); + forwardedPorts.forEach(forwardedPort => { + const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces(this._candidates ?? new Map(), LOCALHOST_ADDRESSES[0], forwardedPort.port) ?? forwardedPort; + if (matchingCandidate) { + matchingCandidates.set(forwardedPort.port, matchingCandidate); + const pid = isCandidatePort(matchingCandidate) ? matchingCandidate.pid : undefined; + if (!pidToPortsMapping.has(pid)) { + pidToPortsMapping.set(pid, []); + } + pidToPortsMapping.get(pid)?.push(forwardedPort.port); + } + }); + + const configAttributes: Map = new Map(); + forwardedPorts.forEach(forwardedPort => { + const attributes = this.configPortsAttributes.getAttributes(forwardedPort.port, forwardedPort.host, matchingCandidates.get(forwardedPort.port)?.detail); + if (attributes) { + configAttributes.set(forwardedPort.port, attributes); + } + }); + if ((this.portAttributesProviders.length === 0) || !checkProviders) { + return (configAttributes.size > 0) ? configAttributes : undefined; + } + + // Group calls to provide attributes by pid. + const allProviderResults = await Promise.all(flatten(this.portAttributesProviders.map(provider => { + return Array.from(pidToPortsMapping.entries()).map(entry => { + const portGroup = entry[1]; + const matchingCandidate = matchingCandidates.get(portGroup[0]); + return provider.providePortAttributes(portGroup, + matchingCandidate?.pid, matchingCandidate?.detail, new CancellationTokenSource().token); + }); + }))); + const providedAttributes: Map = new Map(); + allProviderResults.forEach(attributes => attributes.forEach(attribute => { + if (attribute) { + providedAttributes.set(attribute.port, attribute); + } + })); + + if (!configAttributes && !providedAttributes) { + return undefined; + } + + // Merge. The config wins. + const mergedAttributes: Map = new Map(); + forwardedPorts.forEach(forwardedPorts => { + const config = configAttributes.get(forwardedPorts.port); + const provider = providedAttributes.get(forwardedPorts.port); + mergedAttributes.set(forwardedPorts.port, { + elevateIfNeeded: config?.elevateIfNeeded, + label: config?.label, + onAutoForward: config?.onAutoForward ?? PortsAttributes.providedActionToAction(provider?.autoForwardAction), + requireLocalPort: config?.requireLocalPort, + protocol: config?.protocol + }); + }); + + return mergedAttributes; + } + + addAttributesProvider(provider: PortAttributesProvider) { + this.portAttributesProviders.push(provider); + } +} From 6c20ed925a716d2f81d6456e718dd7375caddf07 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Aug 2023 14:16:54 -0700 Subject: [PATCH 64/69] fix cyclic dependency issue --- .../accessibility/browser/accessibilityCommands.ts | 9 +++++++++ .../contrib/accessibility/browser/accessibleView.ts | 4 ++-- .../accessibility/browser/accessibleViewActions.ts | 5 +++-- 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 src/vs/workbench/contrib/accessibility/browser/accessibilityCommands.ts diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityCommands.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityCommands.ts new file mode 100644 index 00000000000..0e01cbae71f --- /dev/null +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityCommands.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export const enum AccessibilityCommandIds { + OpenAccessibleView = 'editor.action.accessibleView', + OpenAccessibilityHelp = 'editor.action.accessibilityHelp' +} diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 8b9d4ef3d13..61af8548b1e 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -31,8 +31,8 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { AccessibilityCommandIds } from 'vs/workbench/contrib/accessibility/browser/accessibilityCommands'; import { AccessibilityVerbositySettingId, accessibilityHelpIsShown, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; const enum DIMENSIONS { @@ -473,7 +473,7 @@ export class AccessibleViewService extends Disposable implements IAccessibleView if (!this._configurationService.getValue(verbositySettingKey)) { return null; } - const keybinding = this._keybindingService.lookupKeybinding(AccessibleViewAction.id)?.getAriaLabel(); + const keybinding = this._keybindingService.lookupKeybinding(AccessibilityCommandIds.OpenAccessibleView)?.getAriaLabel(); let hint = null; if (keybinding) { hint = localize('acessibleViewHint', "Inspect this in the accessible view with {0}", keybinding); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts index 3d4a87e04dc..298a8dc22e9 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts @@ -10,6 +10,7 @@ import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { AccessibilityCommandIds } from 'vs/workbench/contrib/accessibility/browser/accessibilityCommands'; import { accessibilityHelpIsShown, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; @@ -91,7 +92,7 @@ function registerCommand(command: T): T { } export const AccessibilityHelpAction = registerCommand(new MultiCommand({ - id: 'editor.action.accessibilityHelp', + id: AccessibilityCommandIds.OpenAccessibilityHelp, precondition: undefined, kbOpts: { primary: KeyMod.Alt | KeyCode.F1, @@ -111,7 +112,7 @@ export const AccessibilityHelpAction = registerCommand(new MultiCommand({ export const AccessibleViewAction = registerCommand(new MultiCommand({ - id: 'editor.action.accessibleView', + id: AccessibilityCommandIds.OpenAccessibleView, precondition: undefined, kbOpts: { primary: KeyMod.Alt | KeyCode.F2, From a91672675f1a5120069b6b51401fdc9bd8167b01 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Aug 2023 14:20:35 -0700 Subject: [PATCH 65/69] use AccessibilityCommandIds for more commands --- .../accessibility/browser/accessibilityCommands.ts | 6 +++++- .../accessibility/browser/accessibleViewActions.ts | 8 ++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityCommands.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityCommands.ts index 0e01cbae71f..9f9edcaa0d4 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityCommands.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityCommands.ts @@ -5,5 +5,9 @@ export const enum AccessibilityCommandIds { OpenAccessibleView = 'editor.action.accessibleView', - OpenAccessibilityHelp = 'editor.action.accessibilityHelp' + OpenAccessibilityHelp = 'editor.action.accessibilityHelp', + DisableVerbosityHint = 'editor.action.accessibleViewDisableHint', + GoToSymbol = 'editor.action.accessibleViewGoToSymbol', + ShowNext = 'editor.action.accessibleViewNext', + ShowPrevious = 'editor.action.accessibleViewPrevious' } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts index 298a8dc22e9..5355ae61cef 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts @@ -27,7 +27,7 @@ const commandPalette = { class AccessibleViewNextAction extends Action2 { constructor() { super({ - id: 'editor.action.accessibleViewNext', + id: AccessibilityCommandIds.ShowNext, precondition: accessibleViewIsShown, keybinding: { primary: KeyMod.Alt | KeyCode.BracketRight, @@ -48,7 +48,7 @@ registerAction2(AccessibleViewNextAction); class AccessibleViewPreviousAction extends Action2 { constructor() { super({ - id: 'editor.action.accessibleViewPrevious', + id: AccessibilityCommandIds.ShowPrevious, precondition: accessibleViewIsShown, keybinding: { primary: KeyMod.Alt | KeyCode.BracketLeft, @@ -69,7 +69,7 @@ registerAction2(AccessibleViewPreviousAction); class AccessibleViewGoToSymbolAction extends Action2 { constructor() { super({ - id: 'editor.action.accessibleViewGoToSymbol', + id: AccessibilityCommandIds.GoToSymbol, precondition: accessibleViewIsShown, keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO, @@ -133,7 +133,7 @@ export const AccessibleViewAction = registerCommand(new MultiCommand({ class AccessibleViewDisableHintAction extends Action2 { constructor() { super({ - id: 'editor.action.accessibleViewDisableHint', + id: AccessibilityCommandIds.DisableVerbosityHint, keybinding: { when: ContextKeyExpr.or(accessibleViewIsShown, accessibilityHelpIsShown), primary: KeyMod.Alt | KeyCode.F6, From 0d879f791337255b754ab17e29e065443e2d8340 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Aug 2023 14:27:29 -0700 Subject: [PATCH 66/69] more replacements --- .../accessibility/browser/accessibleView.ts | 14 +++++++------- .../accessibility/browser/accessibleViewActions.ts | 14 +++++++------- .../{browser => common}/accessibilityCommands.ts | 2 +- .../contrib/chat/browser/chatInputPart.ts | 3 ++- .../contrib/inlineChat/browser/inlineChatWidget.ts | 3 ++- .../notebook/browser/notebookEditorWidget.ts | 3 ++- .../contrib/terminal/browser/terminalInstance.ts | 3 ++- .../workbench/contrib/terminal/common/terminal.ts | 3 ++- 8 files changed, 25 insertions(+), 20 deletions(-) rename src/vs/workbench/contrib/accessibility/{browser => common}/accessibilityCommands.ts (94%) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 61af8548b1e..3d3ea56946a 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -31,7 +31,7 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { AccessibilityCommandIds } from 'vs/workbench/contrib/accessibility/browser/accessibilityCommands'; +import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { AccessibilityVerbositySettingId, accessibilityHelpIsShown, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; @@ -299,7 +299,7 @@ class AccessibleView extends Disposable { let helpHint = ''; const verbose = this._configurationService.getValue(provider.verbositySettingKey); if (verbose && provider.options.type === AccessibleViewType.View) { - const accessibilityHelpKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibilityHelp')?.getLabel(); + const accessibilityHelpKeybinding = this._keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getLabel(); if (accessibilityHelpKeybinding) { helpHint = localize('ariaAccessibilityHelp', "Use {0} for accessibility help", accessibilityHelpKeybinding); } @@ -404,8 +404,8 @@ class AccessibleView extends Disposable { private _getNavigationHint(): string { let hint = ''; - const nextKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleViewNext')?.getAriaLabel(); - const previousKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleViewPrevious')?.getAriaLabel(); + const nextKeybinding = this._keybindingService.lookupKeybinding(AccessibilityCommandId.ShowNext)?.getAriaLabel(); + const previousKeybinding = this._keybindingService.lookupKeybinding(AccessibilityCommandId.ShowPrevious)?.getAriaLabel(); if (nextKeybinding && previousKeybinding) { hint = localize('accessibleViewNextPreviousHint', "Show the next ({0}) or previous ({1}) item", nextKeybinding, previousKeybinding); } else { @@ -418,7 +418,7 @@ class AccessibleView extends Disposable { return ''; } let hint = ''; - const disableKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleViewDisableHint', this._contextKeyService)?.getAriaLabel(); + const disableKeybinding = this._keybindingService.lookupKeybinding(AccessibilityCommandId.DisableVerbosityHint, this._contextKeyService)?.getAriaLabel(); if (disableKeybinding) { hint = localize('acessibleViewDisableHint', "Disable the aria label hint to open this ({0})", disableKeybinding); } else { @@ -428,7 +428,7 @@ class AccessibleView extends Disposable { } private _getGoToSymbolHint(providerHasSymbols?: boolean): string { - const goToSymbolKb = this._keybindingService.lookupKeybinding('editor.action.accessibleViewGoToSymbol')?.getAriaLabel(); + const goToSymbolKb = this._keybindingService.lookupKeybinding(AccessibilityCommandId.GoToSymbol)?.getAriaLabel(); let goToSymbolHint = ''; if (providerHasSymbols) { if (goToSymbolKb) { @@ -473,7 +473,7 @@ export class AccessibleViewService extends Disposable implements IAccessibleView if (!this._configurationService.getValue(verbositySettingKey)) { return null; } - const keybinding = this._keybindingService.lookupKeybinding(AccessibilityCommandIds.OpenAccessibleView)?.getAriaLabel(); + const keybinding = this._keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibleView)?.getAriaLabel(); let hint = null; if (keybinding) { hint = localize('acessibleViewHint', "Inspect this in the accessible view with {0}", keybinding); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts index 5355ae61cef..da283f254cc 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts @@ -10,7 +10,7 @@ import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { AccessibilityCommandIds } from 'vs/workbench/contrib/accessibility/browser/accessibilityCommands'; +import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { accessibilityHelpIsShown, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; @@ -27,7 +27,7 @@ const commandPalette = { class AccessibleViewNextAction extends Action2 { constructor() { super({ - id: AccessibilityCommandIds.ShowNext, + id: AccessibilityCommandId.ShowNext, precondition: accessibleViewIsShown, keybinding: { primary: KeyMod.Alt | KeyCode.BracketRight, @@ -48,7 +48,7 @@ registerAction2(AccessibleViewNextAction); class AccessibleViewPreviousAction extends Action2 { constructor() { super({ - id: AccessibilityCommandIds.ShowPrevious, + id: AccessibilityCommandId.ShowPrevious, precondition: accessibleViewIsShown, keybinding: { primary: KeyMod.Alt | KeyCode.BracketLeft, @@ -69,7 +69,7 @@ registerAction2(AccessibleViewPreviousAction); class AccessibleViewGoToSymbolAction extends Action2 { constructor() { super({ - id: AccessibilityCommandIds.GoToSymbol, + id: AccessibilityCommandId.GoToSymbol, precondition: accessibleViewIsShown, keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO, @@ -92,7 +92,7 @@ function registerCommand(command: T): T { } export const AccessibilityHelpAction = registerCommand(new MultiCommand({ - id: AccessibilityCommandIds.OpenAccessibilityHelp, + id: AccessibilityCommandId.OpenAccessibilityHelp, precondition: undefined, kbOpts: { primary: KeyMod.Alt | KeyCode.F1, @@ -112,7 +112,7 @@ export const AccessibilityHelpAction = registerCommand(new MultiCommand({ export const AccessibleViewAction = registerCommand(new MultiCommand({ - id: AccessibilityCommandIds.OpenAccessibleView, + id: AccessibilityCommandId.OpenAccessibleView, precondition: undefined, kbOpts: { primary: KeyMod.Alt | KeyCode.F2, @@ -133,7 +133,7 @@ export const AccessibleViewAction = registerCommand(new MultiCommand({ class AccessibleViewDisableHintAction extends Action2 { constructor() { super({ - id: AccessibilityCommandIds.DisableVerbosityHint, + id: AccessibilityCommandId.DisableVerbosityHint, keybinding: { when: ContextKeyExpr.or(accessibleViewIsShown, accessibilityHelpIsShown), primary: KeyMod.Alt | KeyCode.F6, diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityCommands.ts b/src/vs/workbench/contrib/accessibility/common/accessibilityCommands.ts similarity index 94% rename from src/vs/workbench/contrib/accessibility/browser/accessibilityCommands.ts rename to src/vs/workbench/contrib/accessibility/common/accessibilityCommands.ts index 9f9edcaa0d4..8475978456d 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityCommands.ts +++ b/src/vs/workbench/contrib/accessibility/common/accessibilityCommands.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export const enum AccessibilityCommandIds { +export const enum AccessibilityCommandId { OpenAccessibleView = 'editor.action.accessibleView', OpenAccessibilityHelp = 'editor.action.accessibilityHelp', DisableVerbosityHint = 'editor.action.accessibleViewDisableHint', diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index d326a7ff85a..d1815071ca9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -33,6 +33,7 @@ import { IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chat import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { isMacintosh } from 'vs/base/common/platform'; +import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; const $ = dom.$; @@ -101,7 +102,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private _getAriaLabel(): string { const verbose = this.configurationService.getValue(AccessibilityVerbositySettingId.Chat); if (verbose) { - const kbLabel = this.keybindingService.lookupKeybinding('editor.action.accessibilityHelp')?.getLabel(); + const kbLabel = this.keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getLabel(); return kbLabel ? localize('actions.chat.accessibiltyHelp', "Chat Input, Type to ask questions or type / for topics, press enter to send out the request. Use {0} for Chat Accessibility Help.", kbLabel) : localize('chatInput.accessibilityHelpNoKb', "Chat Input, Type code here and press Enter to run. Use the Chat Accessibility Help command for more information."); } return localize('chatInput', "Chat Input"); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 519d491695c..292aedf6329 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -53,6 +53,7 @@ import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/cha import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -392,7 +393,7 @@ export class InlineChatWidget { } let label = defaultAriaLabel; if (this._configurationService.getValue(AccessibilityVerbositySettingId.InlineChat)) { - const kbLabel = this._keybindingService.lookupKeybinding('editor.action.accessibilityHelp')?.getLabel(); + const kbLabel = this._keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getLabel(); label = kbLabel ? localize('inlineChat.accessibilityHelp', "Inline Chat Input, Use {0} for Inline Chat Accessibility Help.", kbLabel) : localize('inlineChat.accessibilityHelpNoKb', "Inline Chat Input, Run the Inline Chat Accessibility Help command for more information."); } _inputEditorOptions.ariaLabel = label; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 1821928502e..4ccc2ee5de9 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -99,6 +99,7 @@ import { NotebookCellOutlineProvider } from 'vs/workbench/contrib/notebook/brows import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; +import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; const $ = DOM.$; @@ -872,7 +873,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._register(this._listDelegate); const createNotebookAriaLabel = () => { - const keybinding = this.keybindingService.lookupKeybinding('editor.action.accessibilityHelp')?.getLabel(); + const keybinding = this.keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getLabel(); if (this.configurationService.getValue(AccessibilityVerbositySettingId.Notebook)) { return keybinding diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 35059fd4f4b..a699b24c929 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -86,6 +86,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { importAMDNodeModule } from 'vs/amdX'; import { ISimpleSelectedSuggestion } from 'vs/workbench/services/suggest/browser/simpleSuggestWidget'; import type { IMarker, Terminal as XTermTerminal } from 'xterm'; +import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; const enum Constants { /** @@ -1886,7 +1887,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (!screenReaderOptimized) { labelParts.push(nls.localize('terminalScreenReaderMode', "Run the command: Toggle Screen Reader Accessibility Mode for an optimized screen reader experience")); } - const accessibilityHelpKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibilityHelp')?.getLabel(); + const accessibilityHelpKeybinding = this._keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getLabel(); if (this._configurationService.getValue(AccessibilityVerbositySettingId.Terminal) && accessibilityHelpKeybinding) { labelParts.push(nls.localize('terminalHelpAriaLabel', "Use {0} for terminal accessibility help", accessibilityHelpKeybinding)); } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 219ca791ee1..d2fe9ab523a 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -14,6 +14,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { ISerializedCommandDetectionCapability, ITerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/capabilities'; import { IMergedEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; import { ICreateContributedTerminalProfileOptions, IExtensionTerminalProfile, IFixedTerminalDimensions, IProcessDataEvent, IProcessProperty, IProcessPropertyMap, IProcessReadyEvent, IProcessReadyWindowsPty, IShellLaunchConfig, ITerminalBackend, ITerminalContributions, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ProcessPropertyType, TerminalIcon, TerminalLocationString, TitleEventSource } from 'vs/platform/terminal/common/terminal'; +import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { IExtensionPointDescriptor } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -567,7 +568,7 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ TerminalCommandId.AcceptSelectedSuggestion, TerminalCommandId.HideSuggestWidget, TerminalCommandId.FocusHover, - 'editor.action.accessibilityHelp', + AccessibilityCommandId.OpenAccessibilityHelp, 'editor.action.toggleTabFocusMode', 'notifications.hideList', 'notifications.hideToasts', From 66a4d77f9e9a18362733956c8b055efa5c974156 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 9 Aug 2023 14:57:05 -0700 Subject: [PATCH 67/69] Update src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts Co-authored-by: Megan Rogge --- .../contrib/accessibility/browser/accessibilityConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 061d074807a..f818fd8e4b4 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -71,7 +71,7 @@ const configuration: IConfigurationNode = { ...baseProperty }, [AccessibilitySettingId.UnfocusedViewOpacity]: { - description: localize('unfocusedViewOpacity', 'The opacity percentage (0.2 to 1.0) to use for unfocused editors and terminals. This allows dimming of inactive views to make it more obvious what is focused.'), + description: localize('unfocusedViewOpacity', 'The opacity fraction (0.2 to 1.0) to use for unfocused editors and terminals. This will dim inactive views to make the focused views more obvious.'), type: 'number', minimum: 0.2, maximum: 1, From 8429ea98e9d775b8e6461cd84d2f8691f520dbc2 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 9 Aug 2023 23:58:35 +0200 Subject: [PATCH 68/69] Update port attributes arguments to be a property bag (#189976) * Update port attributes arguments to be a property bag Part of #115616 * Gracefully handle API breakage --- src/vs/workbench/api/common/extHostTunnelService.ts | 11 +++++++++-- src/vscode-dts/vscode.proposed.portsAttributes.d.ts | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index 677a37a84c5..6611152a260 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -113,7 +113,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe }); } - async $providePortAttributes(handles: number[], ports: number[], pid: number | undefined, commandline: string | undefined, cancellationToken: vscode.CancellationToken): Promise { + async $providePortAttributes(handles: number[], ports: number[], pid: number | undefined, commandLine: string | undefined, cancellationToken: vscode.CancellationToken): Promise { const providedAttributes: { providedAttributes: vscode.PortAttributes | null | undefined; port: number }[] = []; for (const handle of handles) { const provider = this._portAttributesProviders.get(handle); @@ -121,7 +121,14 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe return []; } providedAttributes.push(...(await Promise.all(ports.map(async (port) => { - return { providedAttributes: (await provider.provider.providePortAttributes(port, pid, commandline, cancellationToken)), port }; + let providedAttributes: vscode.PortAttributes | null | undefined; + try { + providedAttributes = await provider.provider.providePortAttributes({ port, pid, commandLine }, cancellationToken); + } catch (e) { + // Call with old signature for breaking API change + providedAttributes = await (provider.provider.providePortAttributes as any as (port: number, pid: number | undefined, commandLine: string | undefined, token: vscode.CancellationToken) => vscode.ProviderResult)(port, pid, commandLine, cancellationToken); + } + return { providedAttributes, port }; })))); } diff --git a/src/vscode-dts/vscode.proposed.portsAttributes.d.ts b/src/vscode-dts/vscode.proposed.portsAttributes.d.ts index 35626315373..e3cb4b617f9 100644 --- a/src/vscode-dts/vscode.proposed.portsAttributes.d.ts +++ b/src/vscode-dts/vscode.proposed.portsAttributes.d.ts @@ -63,7 +63,7 @@ declare module 'vscode' { * @param commandLine The command line of the process that is listening on the port. If the command line is unknown, undefined will be passed. * @param token A cancellation token that indicates the result is no longer needed. */ - providePortAttributes(port: number, pid: number | undefined, commandLine: string | undefined, token: CancellationToken): ProviderResult; + providePortAttributes(attributes: { port: number; pid?: number; commandLine?: string }, token: CancellationToken): ProviderResult; } /** From bf217d252d28423b2d5ac6b5dd3648b00cee34e8 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 9 Aug 2023 15:27:41 -0700 Subject: [PATCH 69/69] Fix content widget overwritten warning (#190105) --- .../contrib/chat/browser/chatSlashCommandContentWidget.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.ts b/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.ts index 6e5cfc4f39c..e277a405816 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.ts @@ -15,6 +15,7 @@ import * as aria from 'vs/base/browser/ui/aria/aria'; export class SlashCommandContentWidget extends Disposable implements IContentWidget { private _domNode = document.createElement('div'); private _lastSlashCommandText: string | undefined; + private _isVisible = false; constructor(private _editor: ICodeEditor) { super(); @@ -32,11 +33,16 @@ export class SlashCommandContentWidget extends Disposable implements IContentWid } show() { + if (this._isVisible) { + return; + } + this._isVisible = true; this._domNode.toggleAttribute('hidden', false); this._editor.addContentWidget(this); } hide() { + this._isVisible = false; this._domNode.toggleAttribute('hidden', true); this._editor.removeContentWidget(this); }